Compare commits

...

398 commits

Author SHA1 Message Date
Angie Jones
e2eba75707
Remove project relocation announcement from README (#10347)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-07-09 20:25:58 +00:00
Bradley Axen
4b42ccc4e3
Update canonical model inventory (#10352) 2026-07-09 12:14:30 -07:00
Michael Neale
0eabb6ecc1
Add GPT-5.6 family support (#10349) 2026-07-09 18:35:46 +00:00
Angie Jones
c5e1030c45
Fix Desktop ACP shell PATH (#10336)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-07-09 04:55:58 +00:00
Conor Bronsdon
d86063bad9
fix(docs): show manual-setup indicator for extensions without an install link (#10321)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 01:32:52 +00:00
BestCodes
45327dae5f
refactor: misc ask ai bot updates (#10332)
Signed-off-by: BestCodes <106822363+The-Best-Codes@users.noreply.github.com>
2026-07-09 01:31:02 +00:00
Abhijay Jain
d7f8dc62d8
feat(acp): add delete support for custom apps from Apps UI (#10127)
Signed-off-by: Abhijay Jain <Abhijay007j@gmail.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-09 01:13:14 +00:00
Michael Neale
e09289cbac
fix: Dedupe duplicate signed thinking blocks to fix Anthropic 400 (#10080) (#10083)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-08 23:48:18 +00:00
github-actions[bot]
e6be2e9392
chore(release): bump version to 1.42.0 (minor) (#10295)
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Cargo Deny / deny (push) Has been cancelled
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jack Amadeo <jackamadeo@squareup.com>
2026-07-08 18:43:50 +00:00
yyy33
be521ff8b2
Increase the /mcp-app-guest body limit for larger single-file MCP apps (#10324)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-08 17:30:56 +00:00
Harnoor Singh
af34c75fd5
fix(desktop): relabel settings 'Session' tab to 'External Backend' (#10318)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-08 17:29:46 +00:00
Jack Amadeo
e72601ac86
clean up unused easter egg assets (#10312) 2026-07-08 16:58:28 +00:00
Lifei Zhou
ce53dd5526
chore: Simplified the issue templates (#10316)
Some checks are pending
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
Canary / build-cli (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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-07-08 05:00:51 +00:00
morgmart
0118fd5edf
fix(acp): resolve provider default model instead of leaking sentinel "current" (#10305)
Co-authored-by: Jack Amadeo <jackamadeo@squareup.com>
2026-07-08 04:33:06 +00:00
Rob Gray
12f80edee6
feat(providers): add OllamaCloudProvider with dynamic model discovery and context limits (#10264)
Co-authored-by: Angie Jones <jones.angie@gmail.com>
2026-07-08 03:48:55 +00:00
Lifei Zhou
6d699361a0
chore: Remove stale crates/goose-server and update docs (#10224)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-07 23:45:18 +00:00
Jasper
651dc973c8
Update Rust toolchain and raise recursion limit (#10303)
Some checks are pending
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
2026-07-07 22:41:04 +00:00
Jasper
1bced6616b
Switch the local inference provider MLX backend to use the safemlx crate (#10304) 2026-07-07 21:01:15 +00:00
BestCodes
f96f62d985
feat: askai bot hints for followups (#10291)
Some checks are pending
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-07-07 03:27:44 +00:00
filip
af2c765c82
feat(desktop): per-message usage stats UI (tokens, cost, TTFT, tok/s) (#10210)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-06 20:51:06 +00:00
Piyush Bag
8694a8d104
fix(server): return effective context limit from /model-info (#10165) 2026-07-06 15:32:22 +00:00
Rodolfo Olivieri
1857112587
fix(deps): gate rcgen aws_lc_rs feature behind rustls-tls (#10116)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Douwe Osinga <douwe@block.xyz>
2026-07-06 15:32:19 +00:00
Angie Jones
65eed51555
Disable thinking preservation for Groq (#10284)
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
2026-07-06 03:44:59 +00:00
Vincenzo Palazzo
56dc935350
Bound code-mode execution with timeout and cancellation (#10214)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 03:38:15 +00:00
dependabot[bot]
1d51452e60
chore(deps): bump docker/setup-buildx-action from 4.1.0 to 4.2.0 (#10232)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 02:58:38 +00:00
dependabot[bot]
9c48693cea
chore(deps): bump docker/build-push-action from 7.2.0 to 7.3.0 (#10233)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 02:58:34 +00:00
dependabot[bot]
a3f8c6b52a
chore(deps): bump docker/metadata-action from 6.1.0 to 6.2.0 (#10235)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 02:58:29 +00:00
dependabot[bot]
c26dbb26b0
chore(deps): bump mockall from 0.14.0 to 0.15.0 (#10240)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 02:58:25 +00:00
dependabot[bot]
aa466fca28
chore(deps): bump uniffi from 0.31.1 to 0.32.0 (#10247)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 02:58:20 +00:00
dependabot[bot]
df6df144bc
chore(deps): bump sigstore-verify from 0.9.0 to 0.10.0 (#10250)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 02:58:16 +00:00
dependabot[bot]
104bfc0502
chore(deps): bump cmov from 0.5.3 to 0.5.4 (#10282)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 02:57:55 +00:00
dependabot[bot]
861b7fbdbf
chore(deps): bump candle-transformers from 0.10.2 to 0.11.0 (#10242)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-06 02:55:21 +00:00
dependabot[bot]
26ec166ac9
chore(deps): bump candle-core from 0.10.2 to 0.11.0 (#10241)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-06 02:54:54 +00:00
Herley
1b2f77f719
fix: recover malformed tool calls from GLM/Minimax models instead of rejecting them (#10230)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-06 02:37:36 +00:00
dependabot[bot]
f452ea9b3e
chore(deps): bump jsonschema from 0.30.0 to 0.46.9 (#10244)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-05 22:46:24 -04:00
Jack Amadeo
8e18f07487
move google provider into goose-providers (#10216) 2026-07-06 01:51:36 +00:00
dependabot[bot]
4e2a992c5a
chore(deps): bump umya-spreadsheet from 2.3.3 to 3.0.0 (#10248)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-06 01:39:32 +00:00
Jack Amadeo
140158c756
add provider bindings MVP to goose-sdk, and add python wheel publishing (#10208) 2026-07-06 01:15:54 +00:00
dependabot[bot]
782f69f175
chore(deps): bump actions/checkout from 6.0.2 to 7.0.0 (#10236)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-05 22:20:17 +00:00
dependabot[bot]
a9cd4d23ef
chore(deps): bump cashapp/activate-hermit from 01.1.2 to 1.1.5 (#10234)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-05 22:20:13 +00:00
Vincenzo Palazzo
5cf47eea1b
feat(ui): group chat sessions by project in nav panel (#10212)
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-05 22:17:18 +00:00
Jack Amadeo
3eb9f50ae5
move snowflake (#10218)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-07-05 21:51:01 +00:00
Barry
4889c0d405
fix(cli): restore cursor when configure exits (#10222) 2026-07-05 20:59:47 +00:00
filip
9b837f1a46
feat: per-message usage/cost tracking with derived session totals (#10172)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-05 16:39:10 -04:00
Charlie Croom
1df5f60735
Skip scheduled sessions for implicit resume (#10178)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-05 15:54:35 +00:00
Vincenzo Palazzo
c0e10c5778
Drop unsupported gpt-5.3-codex from chatgpt_codex provider (#10271)
Co-authored-by: goose <noreply@anthropic.com>
2026-07-05 13:47:02 +00:00
Barry
80ec6afbc8
docs(cli): clarify session --edit context retention (#10257)
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
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
2026-07-05 13:43:55 +00:00
Vincenzo Palazzo
b72a9c6bc4
Show project names in directory switcher dropdown (#10207)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-07-05 13:33:54 +00:00
Adam Miller
2eea54c202
fix(cli): sync slash help with builtins (#10173) 2026-07-05 09:42:42 +00:00
Osraka
a0aed81f36
fix(providers): treat unicode punctuation as image path terminators (#10106)
Some checks failed
Canary / Prepare Version (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
Create Minor Release PR / check-version-bump-pr (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
Create Minor Release PR / release (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-03 16:23:30 +00:00
Kyle E DeFreitas
d873c91234
fix: don't bleed provider-specific request_params into delegate() sessions (#9906)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-03 16:23:26 +00:00
nuthalapativarun
6d8c42cfa0
fix(ui): purge empty phantom sessions on startup (#9651)
Signed-off-by: Varun Nuthalapati <nuthalapativarun@gmail.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-03 16:23:23 +00:00
Jack Amadeo
c82c431c70
move databricks providers into goose-providers crate (#10162)
Some checks failed
Canary / bundle-desktop-linux (push) Blocked by required conditions
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Cargo Deny / deny (push) Has been cancelled
2026-07-02 16:18:57 +00:00
Jack Amadeo
3254eed181
Move declarative definitions (#10167) 2026-07-02 16:09:20 +00:00
Jasper
79cbb2627d
Fail closed on unverifiable update provenance (#10168)
Co-authored-by: Jasper <jasper@spiral.xyz>
2026-07-02 15:09:38 +00:00
Chih-Tao Lee
de331d78ef
fix(acp): include structuredContent from MCP tool response (#10128) 2026-07-02 14:26:11 +00:00
github-actions[bot]
402ff562b3
chore(release): bump version to 1.41.0 (minor) (#10198)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Lifei Zhou <lifei@squareup.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 12:41:48 +00:00
Jasper
a3854aee3d
Fix canonical model output paths (#10196) 2026-07-02 11:05:16 +00:00
Jasper
ba6fb2b56d
Refactor local inference provider crates (#10169)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-07-02 08:51:53 +00:00
Jasper
bf1a986d5e
Allow local file ACP origins (#10194) 2026-07-02 08:37:06 +00:00
Abhijay Jain
410a78983e
fix(acp): propagate agent cleanup errors on session delete (#10112)
Signed-off-by: Abhijay Jain <Abhijay007j@gmail.com>
2026-07-02 08:06:41 +00:00
石岳峰
dbc1f88ca7
fix(providers): add claude-sonnet-5 to canonical model registry (#10183)
Co-authored-by: syf2211 <syf2211@users.noreply.github.com>
2026-07-02 06:10:35 +00:00
Lifei Zhou
8cb6d4766c
fix: Skip downstream github jobs for release branch build (#10186) 2026-07-02 04:38:40 +00:00
Lifei Zhou
185b185fb8
fix: increased mcp-app-guest request body limit (#10180) 2026-07-02 04:30:53 +00:00
Michael Neale
27958c4c29
Remove open-model-gym eval suite (#10174)
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction 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
2026-07-02 00:31:15 +00:00
Lifei Zhou
b83b194dad
acp migration (ui) : ui connect to acp directly instead of goosed (#10081) 2026-07-02 00:29:29 +00:00
Lifei Zhou
0d4b6924b9
Revert "Bedrock model discovery and validation" (#10176) 2026-07-01 23:39:04 +00:00
Jasper
a162a7f778
Improve ACP auth and origin defaults (#9886) 2026-07-01 22:08:46 +00:00
Gellért Stadler
7026e14585
Bedrock model discovery and validation (#9997) 2026-07-01 21:19:28 +00:00
dependabot[bot]
e5a0162513
chore(deps): bump actions-rust-lang/setup-rust-toolchain from 1.16.1 to 1.17.0 (#10052)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 21:18:09 +00:00
yyy33
9165e2738f
fix: tolerate partial Responses output items (#10108) 2026-07-01 19:56:57 +00:00
Kyle E DeFreitas
44869db2d9
fix(databricks): always use OpenAI-compatible "parameters" for tools (#10101) 2026-07-01 19:56:54 +00:00
Osraka
2d47d1feea
fix(providers): unescape shell-escaped image paths (#10098) 2026-07-01 19:56:52 +00:00
dependabot[bot]
bb1bd2667f
chore(deps-dev): bump js-yaml from 4.1.1 to 4.2.0 in /documentation (#10107)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 19:51:26 +00:00
Jack Amadeo
fc5353a39c
regenerate schema (#10166) 2026-07-01 18:57:45 +00:00
dependabot[bot]
613f66b9f2
chore(deps): bump actions/stale from 10.1.1 to 10.3.0 (#10050)
Some checks are pending
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 18:05:45 +00:00
dependabot[bot]
d9d3fce9b7
chore(deps): bump actions/attest-build-provenance from 4.1.0 to 4.1.1 (#10049)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 18:05:44 +00:00
dependabot[bot]
c595a4ab03
chore(deps): bump pnpm/action-setup from 6.0.8 to 6.0.9 (#9892)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Douwe Osinga <douwe@block.xyz>
2026-07-01 18:05:38 +00:00
Adam Dalloul
a3b4166d4e
Update EmpirioLabs provider display name to EmpirioLabs AI (#10126) 2026-07-01 17:06:38 +00:00
Jack Amadeo
b6349b5125
add declarative provider support to goose-providers crate (#9992) 2026-07-01 16:40:49 +00:00
Douwe Osinga
006d585546
fix(desktop): seed mac auto-update config (#10132)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-01 16:26:40 +00:00
Jack Amadeo
363b309861
Update release testing instructions (#10122) 2026-07-01 14:18:06 +00:00
Douwe Osinga
f150672e23
test: generic validator for declarative providers (#10010)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-07-01 03:13:10 +00:00
Lifei Zhou
82c0b0dfd9
UI acp migratoin: Decouple desktop UI types from generated OpenAPI types (Part 2) (#10149) 2026-07-01 02:44:08 +00:00
Douwe Osinga
66ce9a6beb
Remove MCP sampling support (#10087)
Some checks are pending
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Lifei Zhou <lifei@squareup.com>
2026-07-01 01:53:44 +00:00
Douwe Osinga
c8971fe5fb
Support TLS for ACP serve (#10088)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Lifei Zhou <lifei@squareup.com>
2026-07-01 00:43:18 +00:00
Lifei Zhou
2f32070975
feat (ui): Migrate dictation local model manager to ACP (#10131) 2026-07-01 00:02:52 +00:00
Douwe Osinga
31bc265a69
Fix MCP app sandbox bridge lifecycle (#10064)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-30 23:02:25 +00:00
Lucas Kim
e8891b7bb0
fix(bedrock): send inference config (max_tokens, temperature) on Converse (#9889)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-30 22:59:40 +00:00
Sunil Kumar
12c3e234f5
feat(providers): support OpenRouter request parameters (#9276)
Signed-off-by: sunilkumarvalmiki <g.sunilkumarvalmiki@gmail.com>
Signed-off-by: Douwe Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-30 22:48:03 +00:00
Jasper
d1cc1439ec
Migrate local inference model management to ACP (#10124) 2026-06-30 22:40:04 +00:00
Jack Amadeo
159fb4d53e
(attempt to) fix disk space errors in linux release builds (#10024) 2026-06-30 22:29:53 +00:00
Sean Murphy
20966fd057
feat: add --edit session flag to edit conversation before forking (#9799)
Signed-off-by: Sean Murphy <murphysean84@gmail.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-30 21:15:52 +00:00
FenjuFu
04da8e8702
feat: add iFlytek Spark and Astron MaaS providers (#9837)
Signed-off-by: FenjuFu <fufenjupku@gmail.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-30 21:15:50 +00:00
Harnoor Singh
452ea10123
fix(desktop): dedupe Nostr session deep link imports (#9918)
Signed-off-by: harrykamboj1 <singhharnoor116@gmail.com>
2026-06-30 21:15:48 +00:00
Max Novich
d308b13e75
[codex] Add SessionStart hook parity outside CLI (#9970)
Co-authored-by: Max Novich <mnovich@Maxs-MacBook-Pro.local>
2026-06-30 21:15:46 +00:00
Leo Walker
40bfb137ad
feat(providers): add Fireworks AI declarative provider (#9990)
Signed-off-by: Leo Walker <leowalker89@gmail.com>
2026-06-30 21:15:43 +00:00
Kyle E DeFreitas
c45f757c9d
fix(providers): don't retry deterministically-permanent 400s (thinking-block immutability) (#10005)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-30 21:15:41 +00:00
Rodolfo Olivieri
d056c72a2c
fix(deps): downgrade pkcs8 to v0.10 to match sec1/pkcs1 v0.7 (#10119)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 21:15:38 +00:00
dependabot[bot]
273ddedf21
chore(deps): bump actions/cache from 5.0.2 to 6.0.0 (#10051)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-30 21:07:41 +00:00
Lucas Alvares Gomes
a90430e8e5
Make OpenAI Responses API store param configurable (#10040)
Signed-off-by: Lucas Alvares Gomes <lucasagomes@gmail.com>
2026-06-30 20:00:12 +00:00
Jack Amadeo
1e392330bb
remove unsupported model (#10121) 2026-06-30 19:57:34 +00:00
github-actions[bot]
f1a97fde83
chore(release): bump version to 1.40.0 (minor) (#10099)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jack Amadeo <jackamadeo@squareup.com>
2026-06-30 18:15:28 +00:00
Jack Amadeo
b806204887
move ollama provider into goose-providers (#9986)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / release (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-30 17:28:45 +00:00
Lifei Zhou
808b8ca9ad
UI acp migratoin: Decouple desktop UI types from generated OpenAPI types (#10109) 2026-06-30 12:36:38 +00:00
dorien-koelemeijer
66e981e29a
fix(otel): use async reqwest client so OTLP export works in goose serve mode (#10100)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / Check MSRV (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 / Build Rust Project on Windows (push) Waiting to run
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-30 07:10:36 +00:00
Lifei Zhou
2084c43476
feat (acp): exposed available tools in acp schema (#10097) 2026-06-30 05:43:08 +00:00
filip
d43c692127
perf(providers): keep turn-context out of the Anthropic prefix cache (#10030) 2026-06-30 05:09:37 +00:00
Lifei Zhou
37e62497d4
feat (acp+): Remove REST onboarding setup paths (#10061)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
CI / Lint Rust Code (push) Blocked by required conditions
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-30 00:35:30 +00:00
filip
4778448b49
fix(test): stop flaky unparseable-tool-call test by disabling session naming (#10091) 2026-06-30 00:12:07 +00:00
Lifei Zhou
f81a0e2471
chore: Remove legacy MCP-UI proxy support (#10086) 2026-06-29 22:58:40 +00:00
Jack Amadeo
fe7f16b727
Remove session_id from provider streaming trait methods (#9984) 2026-06-29 22:04:58 +00:00
vvaswani
84d02d6d47
fix(cli): update help text for --session-id (#10077)
Some checks are pending
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
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-intel (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
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: Vikram Vaswani <2571660+vvaswani@users.noreply.github.com>
2026-06-29 15:30:27 +00:00
Alex Hancock
2cc1140dc1
task(acp): upgrade SDK and use new HTTP/WS crate (#10082)
Some checks are pending
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / changes (push) Waiting to run
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-29 05:27:11 +00:00
lunchboxfortwo
a1711e75e2
fix(providers): reject non-object tool-call arguments instead of panicking (#9832)
Signed-off-by: Lunchbox <lunchboxfortwo@gmail.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Signed-off-by: Goose <goose@aaif.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Goose <goose@aaif.dev>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
2026-06-29 04:08:19 +00:00
Ken Jo
ec0ae7e12c
docs: name the message field in the hooks payload guide (#9913)
Some checks are pending
CI / Lint Rust Code (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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-28 20:24:32 +00:00
Adam Miller
71280996d4
fix(cli): save /edit prompts to history (#10011)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-28 16:44:01 +00:00
Yixuan Zhong
9ff7376338
feat(i18n): add fr, de, it, pt, id, ms, vi, zh-TW desktop locales (#10072)
Co-authored-by: Yixuan Zhong <Yixuan.Zhong@aecom.com>
2026-06-28 16:33:11 +00:00
Lifei Zhou
52291e64a7
feat (acp+): Use ACP permission manager for tool permissions (#10066) 2026-06-28 09:54:51 +00:00
Lifei Zhou
8878452785
feat (ui): Remove ACP chat feature flag and turn on chat using ACP (#10062)
Some checks are pending
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / Prepare Version (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-27 23:13:34 +00:00
石岳峰
d2687643da
fix(schedule): use session.message_count for schedule sessions listing (#10026)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-06-27 16:24:54 +00:00
Abhijay Jain
b3c086e47a
feat(acp): migrate getDictationConfig and transcribeDictation to ACP (#10048) 2026-06-27 16:23:44 +00:00
Lifei Zhou
4f7bf8c1e2
fix: sync the sesion store after session provider and model update and also update thinking effort (#10060)
Some checks are pending
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
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 / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-27 07:45:19 +00:00
Lifei Zhou
e2a2a64d3c
feat: handling platform event mcp notification for acp (#10038) 2026-06-27 04:32:26 +00:00
Alex Hancock
98a13fff4b
feat(acp+): config in desktop on ACP+ (#10057) 2026-06-27 03:45:14 +00:00
Alex Hancock
2fda760641
feat(acp+): models/providers in desktop on ACP+ (#9987)
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Cargo Deny / deny (push) Has been cancelled
Deploy Documentation / deploy (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
2026-06-26 17:04:24 +00:00
Bradley Axen
d1d2166190
Support streamed tool calls without indexes (#10023) 2026-06-26 16:09:11 +00:00
Jack Amadeo
35999d49f3
add ACP+ handlers for prompt editing (#10031) 2026-06-26 16:03:31 +00:00
Abhijay Jain
f0a3ed3a55
refactor: replace getTunnelStatus with GOOSE_DISABLE_NOSTR_SHARING config flag (#10044) 2026-06-26 15:57:58 +00:00
Douwe Osinga
d9cf383711
chore: ignore Zed settings (#10043)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-26 14:44:26 +00:00
Lifei Zhou
ec450a630e
fix: removed fallback rest api call in ACP mode for edit in place (#10034) 2026-06-26 14:11:46 +00:00
Abhijay Jain
22883e3704
feat(acp): migrate upsertPermissions to setToolPermissions ACP method (#9999) 2026-06-26 12:16:18 +00:00
Lifei Zhou
59b28e455f
Apply ACP recipe state during session load and fork (#9998) 2026-06-25 23:13:57 +00:00
Lifei Zhou
3c62892d1d
feat: used acp agent capabilities for local Inference support check (#9996) 2026-06-25 23:12:22 +00:00
Jack Amadeo
854fed5e5d
move anthropic provider into goose-providers (#9985) 2026-06-25 23:00:08 +00:00
Douwe Osinga
d1af10c5ad
Migrate Nostr session sharing to ACP (#10022)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-25 22:30:42 +00:00
Jack Amadeo
789717b94a
run all builds on the release branch, before tagging (#10017) 2026-06-25 20:27:27 +00:00
SynthLuvr
37d93ceaca
Add GLM-5.2 support (#9967)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-25 16:59:16 +00:00
Jack Amadeo
9c96bd7ebc
ignore unused on windows (#10009) 2026-06-25 16:59:14 +00:00
Jack Amadeo
8ab0320a7b
Revert "mcp(win): full Windows Job Object cleanup wiring (patch draft… (#10013) 2026-06-25 16:59:12 +00:00
Lifei Zhou
bb4a9a7920
removed unnecessary call setRecipeParams for acp enabled path (#10003) 2026-06-25 16:30:20 +00:00
Douwe Osinga
dc8aedfd6a
ACP Migration: MCP/Goose Apps (#9988)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-25 14:06:49 +00:00
dependabot[bot]
f1bf611a22
chore(deps): bump docker/setup-buildx-action from 3.12.0 to 4.1.0 (#9895)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-25 13:45:22 +00:00
Lifei Zhou
24166fc344
chore: Remove share session via base url (#9994)
Some checks are pending
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-intel (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
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-25 03:01:08 +00:00
Lifei Zhou
db26fb79c9
suppress telemetry event on ui (#9991) 2026-06-25 00:50:27 +00:00
Abhijay Jain
416aad8136
chore: remove gateway REST API and UI (#9989)
Co-authored-by: Lifei Zhou <lifei@squareup.com>
2026-06-25 00:44:18 +00:00
Lifei Zhou
eea698945c
feat (acp): custom methods for managing scheduler (#9972) 2026-06-24 23:11:35 +00:00
Lifei Zhou
09998b12cf
chore: removed the too many tools warning (#9974) 2026-06-24 22:15:47 +00:00
Lifei Zhou
d97d07fe17
chore: clean up ui as we don't have session based permission setting on ui (#9975) 2026-06-24 20:53:41 +00:00
Lifei Zhou
274e9521c6
chore: removed mesh on ui (#9982) 2026-06-24 20:52:47 +00:00
filip
6d7e2da8c4
perf: disable extended thinking for fast-model operations (#9815)
Signed-off-by: Filip Kujawa <filip.j.kujawa@gmail.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-24 19:40:50 +00:00
Theofanis Petkos
0c9acc33c9
fix(providers): Buffer thinking across stream chunks for DeepSeek/Kimi tool calls (#9704)
Signed-off-by: thepetk <thepetk@gmail.com>
2026-06-24 18:46:02 +00:00
Vincenzo Palazzo
63d1456b3b
Fix: surface actionable errors for truncated tool-call arguments (#9946)
Co-authored-by: goose <noreply@aaif.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-24 14:53:58 -04:00
Seydi Charyyev
3a93f56671
fix(desktop): show optimistic toggle state when disabling an extension (#9882)
Signed-off-by: Seydi Charyyev <seydi.charyev@gmail.com>
2026-06-24 14:53:53 -04:00
dependabot[bot]
904de1ba9a
chore(deps): bump webpack-dev-server from 5.2.4 to 5.2.5 in /documentation (#9863)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-24 14:53:49 -04:00
Lifei Zhou
48a2a3d180
created custom methods for agent mention and slash command (#9980) 2026-06-24 17:37:52 +00:00
Max Novich
e51d70f1fa
[codex] Emit assistant reply text in Stop hooks (#9968) 2026-06-24 17:03:01 +00:00
Douwe Osinga
2acae3b243
Expose last message timestamp on sessions (#9961)
Some checks are pending
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / Check MSRV (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 / Build Rust Project on Windows (push) Waiting to run
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-24 16:28:01 +00:00
Lifei Zhou
efafdf72a9
removed tunnel rest api and ui (#9932) 2026-06-24 14:50:07 +00:00
Lifei Zhou
79c6547afb
feat (ui): Use ACP steer to send the message in queue in ACP path (#9957) 2026-06-24 13:15:55 +00:00
Douwe Osinga
e4f8b61b58
Migrate diagnostics to JSON report (#9964)
Some checks are pending
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / Check MSRV (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 / Build Rust Project on Windows (push) Waiting to run
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (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 Binary (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-24 01:32:56 +00:00
Douwe Osinga
6782d1f506
Route MCP elicitations through tool streams (#9943)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-24 01:31:25 +00:00
Jack Amadeo
35711b8691
provider refactor: don't require a model config to create a provider (#9953)
Some checks are pending
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / Check MSRV (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 / Build Rust Project on Windows (push) Waiting to run
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-23 23:18:07 +00:00
Jude Edwards
a25f9e94d3
fix(cost): resolve databricks_v2 pricing and surface cost in standard usage update (#9925)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-23 20:19:14 +00:00
Abhijay Jain
51ccfe5b7d
feat: add streamable HTTP support to deeplink generator (#9954)
Signed-off-by: Abhijay Jain <Abhijay007j@gmail.com>
Co-authored-by: goose <goose@block.xyz>
2026-06-23 20:09:06 +00:00
nuthalapativarun
eef6c2a2fa
fix: remove unused SubagentStart/SubagentStop hook events (#9960)
Signed-off-by: Varun Nuthalapati <nuthalapativarun@gmail.com>
2026-06-23 19:55:50 +00:00
Lifei Zhou
527fcfc288
feat(ui): use acp method to load skills (#9959) 2026-06-23 19:46:33 +00:00
github-actions[bot]
06076f0875
chore(release): bump version to 1.39.0 (minor) (#9810)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-23 18:43:47 +00:00
Lifei Zhou
f38f1acef0
feat: created acp method for managing recipes and UI changes (#9951) 2026-06-23 12:19:43 +00:00
Douwe Osinga
156445ed16
fix(desktop): publish mac updater metadata (#9945)
Some checks are pending
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
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-intel (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
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-23 07:29:17 +00:00
Lifei Zhou
d58f841342
feat (ui) - use acp to manage global config and session extensions (#9948)
Some checks are pending
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / changes (push) Waiting to run
Canary / Upload Install Script (push) Blocked by required conditions
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-23 00:02:55 +00:00
Lifei Zhou
ca8938cb43
added recipe handling in new_session (#9935) 2026-06-22 23:05:19 +00:00
Angie Jones
bc8609485b
docs: correct custom agents guide (#9947) 2026-06-22 22:43:03 +00:00
Rain
20231531ab
fix(desktop): activate hermit on macOS so STDIO extensions resolve node (#9482)
Signed-off-by: Pluviobyte <Pluviobyte@users.noreply.github.com>
Co-authored-by: Pluviobyte <Pluviobyte@users.noreply.github.com>
2026-06-22 20:35:14 +00:00
Angie Jones
b94b5f3d3f
docs: custom agents (#9293) 2026-06-22 19:27:26 +00:00
Hammad Khan
62beebe727
fix(autovisualiser): accept JSON-encoded string for the data parameter (#9838)
Signed-off-by: hammadxcm <hammadkhanxcm@gmail.com>
2026-06-22 19:27:22 +00:00
Jack Amadeo
c346d00d17
move OpenAI provider into goose-providers (#9880) 2026-06-22 19:10:59 +00:00
Jude Edwards
0b9ef09a30
fix(developer/read_image): send a User-Agent on URL image fetches (#9927) 2026-06-22 18:48:34 +00:00
Michael Neale
e944dccf54
mcp(win): full Windows Job Object cleanup wiring (patch draft) (#9835)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: Michael Neale <michael.neale@gmail.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-22 18:43:10 +00:00
jlmalone
867267a5d1
feat(cli): add /status slash command (#9845)
Signed-off-by: Joseph Malone <jlmalone@users.noreply.github.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-22 18:29:54 +00:00
dependabot[bot]
23c535ae4d
chore(deps): bump actions/checkout from 6 to 7 (#9894)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 18:26:41 +00:00
dependabot[bot]
102ff05bd2
chore(deps): bump rand from 0.8.6 to 0.10.1 (#9504)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-22 17:14:08 +00:00
dependabot[bot]
a8f1dd0844
chore(deps): bump docker/metadata-action from 6.0.0 to 6.1.0 (#9893)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 17:14:07 +00:00
dependabot[bot]
69496e1fdb
chore(deps): bump docker/login-action from 4.1.0 to 4.2.0 (#9896)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 17:14:06 +00:00
dependabot[bot]
a829e0ebf0
chore(deps): bump tower-http from 0.6.11 to 0.7.0 (#9899)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 17:14:04 +00:00
dependabot[bot]
c847153757
chore(deps): bump sigstore-verify from 0.8.0 to 0.9.0 (#9900)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 17:14:03 +00:00
dependabot[bot]
27f5c8ea7e
chore(deps): bump tiktoken-rs from 0.11.0 to 0.12.0 (#9901)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 17:14:02 +00:00
dependabot[bot]
ada27af526
chore(deps): bump pkcs8 from 0.10.2 to 0.11.0 (#9903)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 17:14:00 +00:00
dependabot[bot]
155a3cd401
chore(deps): bump tokenizers from 0.22.2 to 0.23.1 (#9904)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 17:13:59 +00:00
Angie Jones
d29aa522aa
Allow unlisted models in search (#9933)
Some checks are pending
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
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-intel (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
Unused Dependencies / machete (push) Waiting to run
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
2026-06-22 08:18:03 +00:00
Angie Jones
22b7437b69
fix: do not pass temperature to ChatGPT Codex provider (#9931) 2026-06-22 07:56:27 +00:00
Lifei Zhou
659257316d
feat(ui): use acp new session on desktop (only for non recipe session) (#9914)
Some checks are pending
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
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 / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-22 01:33:19 +00:00
Lifei Zhou
b893b5f24e
chore: removed create recipe from session modal and rest api (#9916) 2026-06-22 00:49:55 +00:00
Lifei Zhou
2712c066bc
feat (acp): used typed request and response for add, get session extensions (#9890) 2026-06-22 00:41:08 +00:00
Michael Neale
d2a921a75d
feat: add option to disable automatic update downloads (#9872)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / release (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: Douwe Osinga <douwe@sidewalklabs.com>
Co-authored-by: Crazyop757 <83007860+Crazyop757@users.noreply.github.com>
Co-authored-by: Douwe Osinga <douwe@sidewalklabs.com>
2026-06-20 22:41:30 +00:00
Lifei Zhou
6c2ec554de
feat (ui): change cwd using acp and refactor extension in bottom menubar (#9887)
Some checks failed
Canary / Prepare Version (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
Create Minor Release PR / check-version-bump-pr (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Create Minor Release PR / release (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
2026-06-19 16:25:54 +00:00
filip
093d134b0a
feat: track cache tokens for accurate cost reporting (#9752)
Signed-off-by: Filip Kujawa <filip.j.kujawa@gmail.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Signed-off-by: Douwe Osinga <douwe@sidewalklabs.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-19 13:56:15 +00:00
Jasper
e6da0c6a14
Sanitize extension environment maps (#9884)
Signed-off-by: Jasper Hugo <jasper@spiral.xyz>
2026-06-19 13:43:40 +00:00
Abhijay Jain
ad64980c42
fix (desktop): updater to target aaif-goose releases (#9868)
Signed-off-by: Abhijay Jain <Abhijay007j@gmail.com>
2026-06-19 13:23:30 +00:00
Lifei Zhou
9785d063c1
feat (ui): load session using acp (#9875) 2026-06-19 09:20:01 +00:00
Lifei Zhou
e223de2e26
fix (acp): Refresh provider when session working directory changes (#9883)
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Cargo Deny / deny (push) Has been cancelled
2026-06-19 08:54:31 +00:00
Jack Amadeo
5dcd3ff34c
move ApiClient to goose-providers, lifting out TlsConfig (#9869) 2026-06-19 00:51:09 +00:00
Jack Amadeo
0f1846a439
refactor the request logger out of providers (#9847)
Some checks failed
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration 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
2026-06-19 00:46:45 +00:00
Douwe Osinga
08fb6d45f3
View model interactions (#9806)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
2026-06-19 00:28:03 +00:00
Douwe Osinga
09c8d2be5a
Update Moim (#9636)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-18 20:02:40 +00:00
Jack Amadeo
b3cf3cbcff
delete embeddings support (#9865) 2026-06-18 19:22:32 +00:00
Nicholas-Westby
a8b11e39b7
docs(installation): correct whitespace in fenced code block (#9867) 2026-06-18 19:18:32 +00:00
Michael Neale
92644274a2
fix(gcp): use refreshed gcloud token after reauth (retry Vertex AI on 401/403) (#9849)
Signed-off-by: Michael Neale <michael.neale@gmail.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-18 19:17:11 +00:00
Douwe Osinga
fb59045408
Feature AAIF migration blog post (#9866)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-18 18:40:43 +00:00
Jack Amadeo
73fd41d7c5
move the provider trait into goose-providers (#9860) 2026-06-18 18:29:58 +00:00
Douwe Osinga
c72c6531aa
docs: remove DCO references (#9864)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-18 17:54:45 +00:00
Jack Amadeo
80eaf8fa44
move global config access out of ModelConfig, and move ModelConfig into goose_providers (#9769) 2026-06-18 16:33:19 +00:00
San Clemente
4c64375de7
Add Ophis MCP server to the extensions registry (#9861)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Create Minor Release PR / release (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-18 16:17:52 +00:00
Hugues Clouâtre
d6393d71a5
feat(aws_bedrock): support OpenAI GPT models via Bedrock mantle endpoint (#9707)
Signed-off-by: Hugues Clouâtre <hugues@linux.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-18 14:21:00 +00:00
Michael Neale
c524c27189
fix(goal): start a turn when /goal or /grind is set (#9801)
Signed-off-by: Michael Neale <michael.neale@gmail.com>
2026-06-18 14:13:28 +00:00
jeffa-block
6d3620d66f
feat(summon): add peek mode for async background tasks (#9519)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-18 13:46:06 +00:00
Hosoeng Lee
f108282f03
Add Korean localization to desktop app (#9856)
Signed-off-by: lhs <lhs@lhsui-Macmini.local>
Signed-off-by: soolmuk <72430756+soolmuk@users.noreply.github.com>
Co-authored-by: lhs <lhs@lhsui-Macmini.local>
2026-06-18 13:43:00 +00:00
Lifei Zhou
7130cbf33e
feat (ui): chat reply UI with ACP (last part) (#9857) 2026-06-18 11:13:35 +00:00
Lifei Zhou
ddf23a0b64
feat (ui): Add durable ACP chat session state for desktop UI (#9852)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / release (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-18 08:06:14 +00:00
Lifei Zhou
802dd39165
feat: use acp for desktop chat prompt (feature toggle off) (#9802)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-18 00:13:08 +00:00
Lifei Zhou
109bda35ac
refactor: make session.name the source of truth for displayed session titles (#9841)
Some checks are pending
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-intel (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
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-17 22:18:28 +00:00
Lifei Zhou
69b4d3686c
fix: remove hard-coded React alias that points to the wrong location and break the dev render (#9842) 2026-06-17 21:28:24 +00:00
Michael Neale
bd7f4924bb
fix: pass thinking config to Bedrock Anthropic models (#9794)
Signed-off-by: Michael Neale <michael.neale@gmail.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 20:44:01 +00:00
Jack Amadeo
c239ae0042
Split provider inventory out of providers (#9826) 2026-06-17 20:42:38 +00:00
Jack Amadeo
b0f5db2a07
Support MCP extensions in open plugins (#9471)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 18:52:54 +00:00
Douwe Osinga
1701f5279a
Add self-improving agents blog post (#9846)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 18:51:23 +00:00
jlmalone
633df013ca
perf(build): drop debug info for dependencies in the dev profile (#9843)
Signed-off-by: Joseph Malone <jlmalone@users.noreply.github.com>
2026-06-17 18:11:53 +00:00
Jack Amadeo
f0cc765440
check for responses API support in databricks (#9347)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 18:00:46 +00:00
Jack Amadeo
177cf3d4b8
Initial prompt with goose://new-session (#9427)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 18:00:44 +00:00
jh-block
3d405249a6
fix(local-inference): use media marker parts for vision prompts (#9452)
Signed-off-by: jh-block <jhugo@block.xyz>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 18:00:40 +00:00
Kyle E DeFreitas
d2090b1de5
feat(gym): make Open Model Gym output dir configurable (#9789)
Signed-off-by: Kyle De Freitas <kdefreitas@squareup.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 17:20:56 +00:00
Seydi Charyyev
1a500d8f48
fix(desktop): keep extensions icon visible after switching sessions (#9787)
Signed-off-by: Seydi Charyyev <seydi.charyev@gmail.com>
Signed-off-by: Angie Jones <jones.angie@gmail.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Angie Jones <jones.angie@gmail.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 17:17:24 +00:00
Jose Manuel Perez
56812f4617
i18n: add Spanish (es) locale support (#9833)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:39:22 +00:00
Thump604
80a286da8c
fix(summon): skip non-recipe project config files (#9808)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 16:23:08 +00:00
Michael Neale
e3dbb86742
Dedup duplicate tool-call ids within a turn (#9792)
Signed-off-by: Michael Neale <michael.neale@gmail.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 16:19:58 +00:00
Darren Xu
9f6eb65ad1
fix: resolve bundled extensions from discovery (#9759)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 16:10:35 +00:00
Jack Amadeo
184f9efda2
Keep the version-bump PR up to date by re-running on merge conflicts (#9761)
Some checks are pending
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / Test and Lint Electron Desktop App (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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Canary / Upload Install Script (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-17 15:43:23 +00:00
Adam Dalloul
402afa5a8c
feat(providers): add EmpirioLabs as a declarative OpenAI-compatible provider (#9771)
Signed-off-by: Adam Dalloul <47503782+Adam-Dalloul@users.noreply.github.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 14:38:45 +00:00
jh-block
4aa5de150a
Add support for MLX models to the local inference provider (#9154)
Signed-off-by: jh-block <jhugo@block.xyz>
2026-06-17 13:32:07 +00:00
Roman Ponomarev
86846ec325
fix(acp): include agentInfo in initialize response (#9765)
Signed-off-by: Roman Ponomarev <maksugr@gmail.com>
2026-06-17 09:49:52 +00:00
Michael Neale
739d9cb276
Fix custom OpenAI provider dropping port when URL scheme is omitted (#9730)
Signed-off-by: Michael Neale <michael.neale@gmail.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 08:50:15 +00:00
Michael Neale
f7b32dab32
fix(desktop): preserve Windows backslash paths in custom extension command (#9741)
Signed-off-by: Michael Neale <michael.neale@gmail.com>
2026-06-17 08:47:44 +00:00
Lifei Zhou
55dd84775b
fixed acp cancel race condition (#9804)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-17 05:47:50 +00:00
Lifei Zhou
72da97204e
feat: implement acp method for elicitation and elicitation improvement (#9797) 2026-06-17 03:40:10 +00:00
Lifei Zhou
1f0f5d90cb
fix: removed window goosed when closing window (#9818) 2026-06-17 03:39:25 +00:00
Douwe Osinga
9804a8c782
View json (#9678)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 02:30:09 +00:00
Douwe Osinga
5adaebb867
Make the context exceeded checker more precise (#9831)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 02:25:29 +00:00
Michael Neale
b0d8ea055c
feat: load global hints from ~/.agents/AGENTS.md (#9736)
Signed-off-by: Michael Neale <michael.neale@gmail.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 02:25:27 +00:00
Michael Neale
0b95199896
fix: inherit login-shell PATH in spawned subagents (#9737)
Signed-off-by: Michael Neale <michael.neale@gmail.com>
2026-06-17 02:24:51 +00:00
Kyle E DeFreitas
d95df17615
feat(gym): make per-agent timeout configurable (GYM_AGENT_TIMEOUT) (#9791)
Signed-off-by: Kyle De Freitas <kdefreitas@squareup.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 02:24:29 +00:00
Michael Neale
7f02c1e539
fix: accept string values for GOOSE_CONTEXT_LIMIT (#9738)
Signed-off-by: Michael Neale <michael.neale@gmail.com>
2026-06-17 02:12:50 +00:00
Hammad Khan
b3d3a8b29e
feat(azure): support Entra ID bearer token auth via AZURE_OPENAI_AD_TOKEN (#9716)
Signed-off-by: hammadxcm <hammadkhanxcm@gmail.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 02:09:05 +00:00
qybaihe
9ca81e78d7
fix: clear rejected OAuth credentials after refresh (#9694)
Signed-off-by: qybaihe <qybaihe@gmail.com>
2026-06-17 02:01:47 +00:00
Jack Amadeo
02d56dc557
use windows-2022 image for CUDA builds (#9834) 2026-06-17 01:48:19 +00:00
filip
75f7d08254
fix: keep extended thinking within the Anthropic output cap (#9814)
Signed-off-by: Filip Kujawa <filip.j.kujawa@gmail.com>
2026-06-17 00:40:45 +00:00
yibo365
e990ab764e
fix(packaging): disable RPM build-id links (#9671)
Signed-off-by: weihua <xiaowh52@gmail.com>
2026-06-17 00:40:43 +00:00
Steve Beaulac
34b20e3083
fix(providers/openai): preserve custom API path in base_url derivation (#9649)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-17 00:40:40 +00:00
Dmitry Beskov
c9a82f3607
feat(i18n): language selection feature (#9405)
Signed-off-by: Dmitry Beskov <43372966+besdar@users.noreply.github.com>
2026-06-17 00:40:38 +00:00
Koen van de Glind
ddd9d3faa9
fix(desktop): handle resume deep links (#9298)
Co-authored-by: McGluut <189849001+McGluut@users.noreply.github.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
2026-06-17 00:40:36 +00:00
Lifei Zhou
e783d2a512
feat: added _meta.conversationBefore for ACP forking session (#9821) 2026-06-17 00:33:55 +00:00
dependabot[bot]
b3d64bb848
chore(deps): bump shlex from 1.3.0 to 2.0.1 (#9505)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-16 22:12:21 +00:00
dependabot[bot]
2e2b7e6860
chore(deps): bump actions/deploy-pages from 4.0.5 to 5.0.0 (#9499)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-16 22:12:16 +00:00
dependabot[bot]
59f0bdbc41
chore(deps): bump actions/upload-pages-artifact from 4.0.0 to 5.0.0 (#9497)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-16 22:12:04 +00:00
dependabot[bot]
943f3e9352
chore(deps): bump thiserror from 1.0.69 to 2.0.18 (#9506)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-16 18:15:21 -04:00
Douwe Osinga
59521f455f
Improve benchmarking (#9637)
Signed-off-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
2026-06-16 19:13:16 +00:00
John Matthew Tennant
81a8b87a60
bug(hooks): repair PATH for plugin hook commands (#9615)
Signed-off-by: John Tennant <jtennant@squareup.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-16 19:01:17 +00:00
Sanidhya Singh
0b67498ba5
fix(desktop): restore Cmd/Ctrl+T new chat shortcut (#9614)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-16 19:00:51 +00:00
nuthalapativarun
764a33fb0b
feat: add Together AI declarative provider (#9545)
Signed-off-by: Varun Nuthalapati <nuthalapativarun@gmail.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-16 19:00:13 +00:00
Jeremy Dawes
e0492d1e51
fix(ui): lead with extension description, shorten HTTP/SSE transport label (#9653)
Signed-off-by: Jeremy Dawes <jeremy@jezweb.net>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-16 18:59:59 +00:00
Trang Le
ec453a3926
fix: Mention menu icon to open the sidebar (#9643) 2026-06-16 18:51:49 +00:00
Chih-Tao Lee
ff91f0c2b1
fix(ui): dedupe React to prevent multiple instances in renderer (#9631) 2026-06-16 18:51:25 +00:00
BestCodes
fd7a0fef92
fix: add hermit cleanup to uvx and jbang scripts (#9616) 2026-06-16 18:51:05 +00:00
Yufeng He
9278953c0a
fix: use blocking OTLP HTTP exporter (#9599)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-16 18:48:45 +00:00
nuthalapativarun
1512934138
fix: fall back to default provider when resuming session with unavailable provider (#9547)
Signed-off-by: Varun Nuthalapati <nuthalapativarun@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 18:48:08 +00:00
nuthalapativarun
3ee795a652
fix(otel): provide Tokio runtime for OTLP HTTP exporter batch thread (#9541)
Signed-off-by: Varun Nuthalapati <nuthalapativarun@gmail.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-16 18:46:41 +00:00
Lance Colton
11c607c476
openai: read meta.n_ctx from /v1/models to fix context limit for local servers (#9530)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-16 18:45:41 +00:00
bdeenyy
b9af83b760
fix(desktop): align session history scrollbar with window edge (#9601)
Signed-off-by: Denis <i@deenyy.ru>
Co-authored-by: Denis <i@deenyy.ru>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 18:39:42 +00:00
nuthalapativarun
722b3c4c85
fix: refresh session name in sidebar after rename (#9543)
Signed-off-by: Varun Nuthalapati <nuthalapativarun@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 18:34:15 +00:00
Yufeng He
e56c7496b2
fix: show resolved skill supporting file paths (#9584) 2026-06-16 18:32:04 +00:00
Simon Ho
76c8028c26
fix(bedrock): implement real streaming via ConverseStream API (#9579)
Signed-off-by: Simon Ho <simon@holabs.dev>
2026-06-16 18:20:05 +00:00
Yi Liu
4327dee7a3
fix: record OpenAI-native cached_tokens in usage metering (#9829)
Signed-off-by: Yi LIU <yi@quantstamp.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-16 18:15:13 +00:00
Trang Le
02f27b97d5
fix: add option for overriding API Url for Moonshot provider (#9819) 2026-06-16 18:08:20 +00:00
jeffa-block
8659c14021
feat(summon): add context parameter for delegate tasks (#9518)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (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 / Build Rust Project on Windows (push) Waiting to run
CI / changes (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-16 15:21:29 +00:00
jeffa-block
4602a88446
feat(summon): add structured metadata to task load results (#9521)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-16 14:48:37 +00:00
jlmalone
93285db7c9
fix(providers): detect image paths with spaces (#9387)
Signed-off-by: Joseph Malone <jlmalone@users.noreply.github.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-16 14:31:26 +00:00
Yufeng He
e4329c7b62
fix(desktop): restore new chat shortcut navigation (#9528)
Some checks failed
Deploy Documentation / deploy (push) Has been cancelled
Cargo Deny / deny (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-15 20:14:15 +00:00
jeffa-block
5d5986fd4f
fix(summon): evict stale completed tasks to prevent unbounded memory growth (#9514)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-15 20:13:48 +00:00
jeffa-block
bbcbb8b92c
feat(summon): add working_dir override for delegate tasks (#9520)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-15 20:08:12 +00:00
Matt Van Horn
6364087318
oauth: time out the MCP callback wait instead of hanging in WSL (#9536)
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-06-15 20:02:35 +00:00
xilema2
cb6ece752c
feat(providers): add OrcaRouter as a declarative OpenAI-compatible provider (#9515)
Signed-off-by: maxile <xile.ma@myflashcloud.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-15 19:53:44 +00:00
jeffa-block
0ed51a5873
fix(summon): warn on unmatched extension names in delegate (#9513)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-15 19:47:31 +00:00
Mark Lavercombe
f4700ef995
Add Scholar Sidekick tutorial page + anonymous-default auth (v0.8.0) (#9477) 2026-06-15 15:45:07 +00:00
Monroe Williams
3ab8ba1be8
Fix: deleting a recently created session and then creating a new session shows the deleted session's content (#9351)
Signed-off-by: Monroe Williams <monroe@pobox.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-15 15:34:02 +00:00
Matt Van Horn
ee1a2e4950
feat: propagate elicitation decline and cancel actions end-to-end (#9437)
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-15 15:23:22 +00:00
Erik Nilsen
0f8a202d9f
fix(recipe): close modal when canceling parameter form (#9195)
Signed-off-by: Erik Nilsen <enilsen16@live.com>
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
2026-06-15 15:18:39 +00:00
Steve Marshall
c4d2d84827
fix(agent): ensure tool request timestamp precedes tool response timestamp (#9462)
Signed-off-by: Steve Marshall <steve.marshall@fasthosts.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 15:03:59 +00:00
github-actions[bot]
ca2490026e
chore(release): bump version to 1.38.0 (minor) (#9684)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-15 14:43:16 +00:00
Darren Xu
cbff60c946
desktop: fix new chat shortcut (#9659) 2026-06-15 14:32:16 +00:00
dependabot[bot]
1472e7ee89
chore(deps): bump hono from 4.12.18 to 4.12.23 in /evals/open-model-gym/mcp-harness (#9609)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 14:06:57 +00:00
dependabot[bot]
dc94aa6363
chore(deps): bump lru from 0.16.4 to 0.18.0 (#9627)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 14:06:54 +00:00
dependabot[bot]
114ae52b6b
chore(deps): bump uniffi from 0.29.5 to 0.31.1 (#9628)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 14:06:49 +00:00
dependabot[bot]
d6fa46c301
chore(deps): bump lopdf from 0.40.0 to 0.41.0 (#9629)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 14:06:46 +00:00
dependabot[bot]
8494fc9bb1
chore(deps): bump the cargo-minor-and-patch group with 9 updates (#9764)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 14:06:42 +00:00
dependabot[bot]
64f9d61647
chore(deps): bump esbuild and tsx in /evals/open-model-gym/suite (#9775)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 14:06:37 +00:00
dependabot[bot]
2712dd8817
chore(deps): bump aiohttp from 3.13.4 to 3.14.0 in /scripts/provider-error-proxy (#9594)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 14:05:00 +00:00
dependabot[bot]
6ab1831aa5
chore(deps): bump actions/upload-artifact from 4.6.2 to 7.0.1 (#9621)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 14:04:56 +00:00
dependabot[bot]
1e9cad712a
chore(deps): bump astral-sh/setup-uv from 7.2.0 to 8.2.0 (#9622)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 14:04:54 +00:00
dependabot[bot]
3dd881a66d
chore(deps): bump actions/setup-node from 4 to 6 (#9623)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 14:04:52 +00:00
dependabot[bot]
fe1475cfa0
chore(deps): bump dirs from 5.0.1 to 6.0.0 (#9630)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 14:04:46 +00:00
Matt Toohey
b3aa4c2e13
feat(session): add opt-in ACP last message snippets (#9798)
Some checks are pending
Live Provider Tests / Build Binary (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
CI / Test and Lint Electron Desktop App (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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (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 / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: Matt Toohey <contact@matttoohey.com>
2026-06-15 06:28:27 +00:00
Sean Murphy
3063de7584
fix: correctly map ollama_cloud to canonical provider and update max_… (#9639)
Signed-off-by: Sean Murphy <murphysean84@gmail.com>
2026-06-15 02:55:41 +00:00
BestCodes
e612d8120c
fix incorrect max tokens values for groq provider (#9790) 2026-06-15 00:03:50 +00:00
Douwe Osinga
9d166ecee9
Mark stream decode errors retryable (#9723)
Some checks failed
Publish Docker Image / docker (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / Release (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Signed-off-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
2026-06-13 11:50:04 +00:00
Angie Jones
6b9360c74f
Validate desktop i18n catalogs (#9776)
Signed-off-by: Angie Jones <jones.angie@gmail.com>
2026-06-13 07:08:18 +00:00
Michael Neale
0629f9951f
fix: classify Bedrock ValidationException as ExecutionError (#9735)
Signed-off-by: Michael Neale <michael.neale@gmail.com>
2026-06-13 06:49:57 +00:00
N
d6d99f548a
i18n: add Japanese locale support (#9768) 2026-06-13 06:04:06 +00:00
Kunall Banerjee
f40d56fe09
docs: update docs for ACP clients (#9772)
Some checks failed
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (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 / Build Rust Project on Windows (push) Waiting to run
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
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
Signed-off-by: Kunall Banerjee <hey@kimchiii.space>
2026-06-13 05:23:27 +00:00
dorien-koelemeijer
8b5a7b8f8e
feat(security): unified OTLP logging schema for cross-tool detection (#9713)
Some checks failed
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Canary / bundle-desktop (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Cargo Deny / deny (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
2026-06-12 04:32:37 +00:00
Kalvin C
f23347fc30
fix: page through all Databricks AI Gateway v2 endpoints when listing models (#9753)
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
2026-06-11 23:53:02 +00:00
jh-block
86e2f04264
Add canonical thinking modes (#9743)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: jh-block <jhugo@block.xyz>
2026-06-11 15:59:58 +00:00
Matt Van Horn
c6a3b5b4e1
fix: preserve unparseable extension entries during config refresh (#9439)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Co-authored-by: Michael Neale <michael.neale@gmail.com>
2026-06-11 05:10:06 +00:00
Abhijay Jain
db1f0fcc63
feat(lang): add Hindi Desktop locale (#9733)
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Cargo Deny / deny (push) Has been cancelled
Deploy Documentation / deploy (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Signed-off-by: Abhijay Jain <Abhijay007j@gmail.com>
2026-06-11 04:38:32 +00:00
Michael Neale
4207b7e080
docs: fix stale session navigation/delete docs (Session History) (#9727)
Signed-off-by: Michael Neale <michael.neale@gmail.com>
2026-06-11 02:17:02 +00:00
Lifei Zhou
523aaee24b
feat: custom acp method to get session info (#9729) 2026-06-11 01:37:50 +00:00
Kalvin C
e8d5622630
feat(acp): support GOOSE_SERVER__SECRET_KEY at goose serve acp endpoint (#9726)
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
2026-06-10 23:32:25 +00:00
Kalvin C
32722e6ef3
feat: surface Anthropic stream refusals as visible errors (#9724)
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
2026-06-10 22:40:27 +00:00
Lifei Zhou
58185fe4ae
pin goose-sdk package in tui (#9712) 2026-06-10 22:00:01 +00:00
Lifei Zhou
be91189afb
feat: use acp search session in Desktop (#9717) 2026-06-10 21:42:54 +00:00
Michael Neale
40315b5ac2
feat: steering messages with ACP (#9560)
Signed-off-by: Michael Neale <michael.neale@gmail.com>
2026-06-10 21:05:30 +00:00
Douwe Osinga
fde407c9d3
Update analyze extension instructions (#9585)
Signed-off-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
2026-06-10 20:51:00 +00:00
Nukually
9455f8a56a
docs: fix typo in MCP blog post (#9641)
Some checks are pending
Deploy Documentation / deploy (push) Waiting to run
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Signed-off-by: shenzhengyang <shenzhengyang@soundws.com>
Co-authored-by: shenzhengyang <shenzhengyang@soundws.com>
2026-06-10 18:20:04 +00:00
Lifei Zhou
d2ab78604e
feat: acp list session with keyword and type filter (#9695)
Some checks are pending
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-10 10:26:46 +00:00
morgmart
10e665a9b9
expose ACP thinking effort config option (#9711)
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Co-authored-by: Lifei Zhou <lifei@squareup.com>
2026-06-10 09:20:14 +00:00
dorien-koelemeijer
390dfb8c78
feat(security): Re-adjust pattern-based detection for prompt injection detection confidence scores (#9690)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-10 00:41:05 +00:00
dorien-koelemeijer
f52d717dce
feat(security): enable prompt injection mitigation by default for internal users via non-overridable env vars (#9612) 2026-06-10 00:13:29 +00:00
Bradley Axen
d396767be8
chore: refresh canonical model registry (#9709)
Signed-off-by: Bradley Axen <baxen@squareup.com>
2026-06-09 23:39:32 +00:00
Lifei Zhou
f4ecdaefc0
feat: use acp list sessions and manage sessions in Desktop (#9687) 2026-06-09 21:48:35 +00:00
Lifei Zhou
e3090836e4
fix: goose-sdk release compat check with new schema (#9697) 2026-06-09 13:53:45 +00:00
Lifei Zhou
0ab4b84104
chore: pause tui release (#9685) 2026-06-09 07:06:06 +00:00
Lifei Zhou
8eb6cd60d6
fix: compatibility of config extension acp call in TUI (#9683) 2026-06-09 02:57:31 +00:00
Jack Amadeo
e5fd568e01
move formats/openai.rs into goose-providers crate, along with several dependencies (#9633) 2026-06-08 15:26:33 +00:00
Douwe Osinga
4904e3c856
Image read tool (#9607)
Some checks failed
Cargo Deny / deny (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Co-authored-by: Douwe Osinga <douwe@squareup.com>
2026-06-05 20:50:34 +00:00
Jack Amadeo
826f13f343
create goose-providers crate with canonical models, conversation and other types (#9588) 2026-06-05 19:28:50 +00:00
Lifei Zhou
2f99664222
feat: acp methods for config extensions (#9581)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are 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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-05 08:08:19 +00:00
Lifei Zhou
65b72b2988
refactor: remove dead component and useNavigationSessions cleanup (#9603) 2026-06-05 06:55:19 +00:00
Lifei Zhou
ec519eeaaf
feat: Only send custom notifications when ACP client specifies this capability in the initialization request (#9596)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-04 22:39:59 +00:00
Alex Hancock
6251e56347
feat(sdk): minimal uniffi setup for cross language sdk (#9593)
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are 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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Cargo Deny / deny (push) Has been cancelled
2026-06-04 13:58:31 +00:00
Lifei Zhou
dc59e41945
Lifei/acp session setup refactor (#9488)
Some checks are pending
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
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-intel (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
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-06-04 03:12:04 +00:00
dependabot[bot]
1cc5aa690a
chore(deps): bump pnpm/action-setup from 6.0.5 to 6.0.8 (#9500)
Some checks are pending
CI / changes (push) Waiting to run
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 13:38:50 -04:00
dependabot[bot]
9646f709db
chore(deps): bump ossf/scorecard-action from 2.4.1 to 2.4.3 (#9501)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 13:38:27 -04:00
dependabot[bot]
34f33fc006
chore(deps): bump EmbarkStudios/cargo-deny-action from 2.0.19 to 2.0.20 (#9498)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 13:37:57 -04:00
dependabot[bot]
1a52f21c08
chore(deps): bump tokenizers from 0.21.4 to 0.22.2 (#9503)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 13:37:16 -04:00
dependabot[bot]
1205a4a042
chore(deps): bump clap_mangen from 0.2.33 to 0.3.0 (#9508)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 13:36:29 -04:00
dependabot[bot]
dc3a63a469
chore(deps): bump strum from 0.27.2 to 0.28.0 (#9509)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 13:35:52 -04:00
dependabot[bot]
1ab48f5dca
chore(deps): bump pkcs8 from 0.10.2 to 0.11.0 (#9510)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 13:35:18 -04:00
dependabot[bot]
302dafc41b
chore(deps): bump mockall from 0.13.1 to 0.14.0 (#9511)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 13:34:15 -04:00
dependabot[bot]
0e4a367db1
chore(deps): bump the cargo-minor-and-patch group across 1 directory with 10 updates (#9587)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 13:25:23 -04:00
Alex Hancock
83a4abf3ea
fix: quote release PR search phrase in pre-release.sh (#9573) 2026-06-03 16:40:14 +00:00
jh-block
08e7480513
Use LRU cache for token counting (#9586)
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / Build Rust Project on Windows (push) Waiting to run
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 / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration 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
Signed-off-by: jh-block <jhugo@block.xyz>
2026-06-03 16:04:54 +00:00
Lifei Zhou
f6caaa03b5
Fixed intermittent missing extension override on ui and cleanup (#9575)
Signed-off-by: Angie Jones <jones.angie@gmail.com>
Co-authored-by: Angie Jones <jones.angie@gmail.com>
2026-06-03 15:14:30 +00:00
jh-block
30034b9b32
Add Hugging Face OAuth support, add auth tab to settings (#9552)
Signed-off-by: jh-block <jhugo@block.xyz>
2026-06-03 13:30:33 +00:00
Lucas Conti
9626b4c3c1
Replace review subprocess timeout with turn limits (#9571)
Some checks are pending
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Prepare Version (push) Waiting to run
Canary / build-cli (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 20:45:24 +00:00
Douwe Osinga
003252fb52
Import sesssions (#9474)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
2026-06-02 20:35:57 +00:00
Douwe Osinga
cd12199604
Bench marking (#9465)
Signed-off-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 20:13:48 +00:00
github-actions[bot]
502ee508d6
chore(release): release version 1.37.0 (#9565)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-02 20:03:27 +00:00
dorien-koelemeijer
030dbb0c51
feat(security): Add directionality to egress logging (#9546) 2026-06-02 18:44:38 +00:00
Douwe Osinga
7982086893
Pick the last canonical model (#9568)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
2026-06-02 17:56:29 +00:00
github-actions[bot]
fdc1994bc1
chore(release): bump version to 1.37.0 (minor) (#9557)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-02 14:40:15 +00:00
Alex Hancock
942a4564e3
Revert "refactor: convert desktop v1 and goose-server extensions to ACP+ (#9448)" (#9564) 2026-06-02 09:44:18 -04:00
Cameron Yick
586bb15d40
fix(extension-manager): forward custom headers through OAuth connect path (#9388)
Some checks are pending
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
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 / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
CI / changes (push) Waiting to run
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: Cameron Yick <cameron.yick@datadoghq.com>
2026-06-02 01:16:42 +00:00
Angie Jones
cd8f718bbb
Fix scheduled recipe session params (#9553)
Signed-off-by: Angie Jones <jones.angie@gmail.com>
2026-06-01 23:28:58 +00:00
Joah Gerstenberg
f69a1780da
fix(otel): skip OTLP signals when protocol=grpc to avoid background-thread panic (#9512)
Some checks are pending
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 / Smoke Tests (push) Blocked by required conditions
Canary / Prepare Version (push) Waiting to run
Canary / build-cli (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Co-authored-by: Joah's AI agent <noreply@blockxyz.com>
Co-authored-by: Amp <amp@ampcode.com>
2026-06-01 21:10:19 +00:00
John Matthew Tennant
5e160e51e4
Honor blocking Stop hook decisions (#9468)
Signed-off-by: John Tennant <jtennant@squareup.com>
2026-06-01 18:59:45 +00:00
Bradley Axen
5508079bba
chore: update canonical model registry (#9551)
Signed-off-by: Bradley Axen <baxen@squareup.com>
2026-06-01 16:05:50 +00:00
Michael Neale
8af2f7609a
feat(providers): add xAI SuperGrok OAuth subscription provider (#9420)
Some checks are pending
Live Provider Tests / check-fork (push) Waiting to run
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: Michael Neale <michael.neale@gmail.com>
2026-06-01 02:19:22 +00:00
Kalvin C
13f7be2ed5
feat: replay acp images on session load (#9496)
Some checks failed
Unused Dependencies / machete (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
2026-05-29 19:34:25 +00:00
Bradley Axen
25ff547487
Expose raw provider supported models over ACP (#9475)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-authored-by: Matt Toohey <contact@matttoohey.com>
2026-05-29 02:18:42 +00:00
Matt Toohey
a3bdb918e7
fix(acp): forward ACP server context window size to clients (#9455)
Some checks are pending
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
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-intel (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
Unused Dependencies / machete (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / changes (push) Waiting to run
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: Matt Toohey <contact@matttoohey.com>
2026-05-29 01:05:45 +00:00
Bradley Axen
104cc17758
Add ACP session system prompt setter (#9478)
Signed-off-by: Bradley Axen <baxen@squareup.com>
2026-05-29 00:35:12 +00:00
Mark Lavercombe
1cb5cb06a3
Add Scholar Sidekick MCP extension (#9433)
Some checks failed
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
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-intel (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
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Cargo Deny / deny (push) Has been cancelled
Deploy Documentation / deploy (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
2026-05-28 14:15:41 +00:00
Rodolfo Olivieri
2116f88908
feat: add tui feature flag to gate the tui command (#9428)
Signed-off-by: Rodolfo Olivieri <rolivier@redhat.com>
2026-05-28 14:12:31 +00:00
Jack Amadeo
d10d009b97
CLI to list skills with token counts (#9326) 2026-05-28 14:09:11 +00:00
Quentin Champenois
27d68ba636
doc: Add Scaleway provider (#9423)
Co-authored-by: Quentin Champenois <qchampenois@scaleway.com>
2026-05-28 10:07:08 -04:00
Alex Hancock
9c403b1560
refactor: convert desktop v1 and goose-server extensions to ACP+ (#9448)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Deploy Documentation / deploy (push) Waiting to run
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Ask AI Bot Docker Image / docker (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
2026-05-27 20:59:48 +00:00
dependabot[bot]
c9945bca5d
chore(deps): bump sha2 from 0.10.9 to 0.11.0 (#8963)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jack Amadeo <jackamadeo@squareup.com>
2026-05-27 19:08:54 +00:00
James Liounis
e9b0d9247b
feat(providers): add Perplexity as a declarative OpenAI-compatible provider (#9324) 2026-05-27 19:07:03 +00:00
Jeremy Dawes
4c88f4b91c
feat(providers): add Alibaba (Qwen via DashScope) declarative provider (#9443)
Signed-off-by: Jeremy Dawes <jeremy@jezweb.net>
2026-05-27 18:45:48 +00:00
Asish Kumar
a18b92e62d
fix(desktop): refresh provider list in Switch Models picker (#9408)
Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
2026-05-27 18:38:14 +00:00
Michael Neale
35d1fc7c51
fix(desktop): start new chat in current window from recipe param modal (#9422)
Signed-off-by: Michael Neale <micn@block.xyz>
2026-05-27 18:35:17 +00:00
Douwe Osinga
1125e8dd54
fix: make azure api-version query param optional (#9221)
Signed-off-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
2026-05-27 18:32:19 +00:00
Douwe Osinga
10ac6b18c9
feat: make tool output size limit configurable via GOOSE_MAX_TOOL_RESPONSE_SIZE (#9256)
Signed-off-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
2026-05-27 18:31:22 +00:00
UGBOMEH OGOCHUKWU WILLIAMS
4f43ae4cd0
fix(ui): preserve pending env vars in Add Extension form (#9285)
Signed-off-by: UGBOMEH OGOCHUKWU WILLIAMS <williamsugbomeh@gmail.com>
Signed-off-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
2026-05-27 18:26:35 +00:00
Angie Jones
d017295f32
fix: tolerate missing responses output (#9449)
Signed-off-by: Angie Jones <jones.angie@gmail.com>
2026-05-27 18:05:00 +00:00
jh-block
27b41d93f5
local inference: stricter GGUF requirements, auto detection of tool calling support, fixed thinking output parsing (#9442)
Signed-off-by: jh-block <jhugo@block.xyz>
2026-05-27 18:00:30 +00:00
Bradley Axen
d90b349a69
feat: add /model slash command to CLI for session model switching (#8747)
Some checks are pending
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
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-intel (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
2026-05-27 13:38:59 +00:00
88plug
794402d932
fix(ci): build linux x86_64 standard inside manylinux_2_28 for glibc 2.28+ compat (#9415)
Signed-off-by: Andrew Mello <andrew@88plug.com>
Co-authored-by: Alex Hancock <alex@alexhancock.com>
Co-authored-by: jh-block <255854896+jh-block@users.noreply.github.com>
2026-05-27 12:58:46 +00:00
Bradley Axen
17493540e1
Prefer goose aliases for Databricks v2 inventory (#9430)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
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 / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: Bradley Axen <baxen@squareup.com>
2026-05-27 01:32:32 +00:00
Bradley Axen
7dc904e1e2
add databricks ai gateway provider (#9274)
Some checks are pending
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-intel (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Cargo Deny / deny (push) Waiting to run
Unused Dependencies / machete (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / changes (push) Waiting to run
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (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 Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
Signed-off-by: Bradley Axen <baxen@squareup.com>
2026-05-26 22:45:01 +00:00
954 changed files with 186750 additions and 92755 deletions

View file

@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
type: bug
assignees: ''
---

View file

@ -1,8 +1 @@
blank_issues_enabled: false
contact_links:
- name: goose Discord discussion
url: https://discord.gg/goose-oss
about: Please ask and answer questions here.
- name: Report a security vulnerability
url: https://github.com/aaif-goose/goose/security/policy
about: Please report security vulnerabilities here.

View file

@ -2,11 +2,14 @@
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'enhancement'
type: 'feature'
assignees: ''
---
**Before opening a feature request**
Please start a discussion in the [goose-eng Discord channel](https://discord.com/channels/1287729918100246654/1514412780504088677) before creating a feature request or beginning implementation. This helps align on direction before anyone spends time building.
**Please explain the motivation behind the feature request.**
Does this feature solve a particular problem you have been experiencing? What opportunities or use cases would be unlocked with this feature?

View file

@ -1,99 +0,0 @@
name: 🧑‍🍳 Submit a recipe to the goose cookbook
description: Share a reusable goose recipe with the community!
title: "[Recipe] <your recipe title here>"
labels: ["recipe submission"]
body:
- type: markdown
attributes:
value: |
Thanks for contributing to the goose cookbook! 🍳
Recipes are reusable sessions created in goose desktop or CLI and shared with the community to help others vibe code faster.
📌 **How to Submit**
- Create your recipe using goose ("Make recipe from this session")
- Fill out the YAML below using the format provided
- Paste it into the field and submit the issue — we'll review and add it to the cookbook!
🪄 **What Happens After?**
- If accepted, we'll publish your recipe to the [goose recipes cookbook](https://goose-docs.ai/recipes)
- Your GitHub handle will be displayed and linked on the recipe card
- If the YAML has any issues, goose will comment with validation errors so you can fix and resubmit.
🧪 **Pro Tip:** You can test your recipe locally in your terminal with:
`goose recipe validate your-recipe.yaml`
- type: textarea
id: recipe-yaml
attributes:
label: Paste Your Full Recipe YAML Below
description: Use the structure below and we'll auto-fill your GitHub handle for `author.contact` after submission.
placeholder: |
version: "1.0.0"
id: clean-up-feature-flag
title: Clean Up Feature Flag
description: Automatically clean up all references of a fully rolled out feature flag from a codebase and make the new behavior the default.
instructions: |
Your job is to systematically remove a fully rolled out feature flag and ensure the new behavior is now the default. Use code search tools like ripgrep to identify all references to the flag, clean up definition files, usage sites, tests, and configuration files. Then create a commit and push changes with clear commit messages documenting the flag removal.
prompt: |
Task: Remove a feature flag that has been fully rolled out, where the feature flag's functionality should become the default behavior.
Context:
Feature flag key: {{ feature_flag_key }}
Project: {{ repo_dir }}
Steps to follow:
1. Check out a *new* branch from main or master named using the feature flag key.
2. Find the feature flag constant/object that wraps the key.
3. Search for all references to the constant/object using ripgrep or equivalent tools.
4. Remove all conditional logic and make the new behavior default.
5. Remove unused imports, mocks, config, and tests.
6. Commit your changes and push the branch.
7. Open a GitHub PR.
Use commit messages like:
chore(flag-cleanup): remove <feature_flag_key> flag from codebase
parameters:
- key: feature_flag_key
input_type: string
requirement: required
description: Key of the feature flag
- key: repo_dir
input_type: string
requirement: optional
default: ./
description: Directory of the codebase
extensions:
- type: stdio
name: developer
cmd: uvx
args:
- developer-mcp@latest
timeout: 300
bundled: true
description: Access developer tools
activities:
- Remove feature flag definitions
- Clean up feature flag usage sites
- Update affected tests
- Remove flag configurations
- Document flag removal
validations:
required: true
- type: markdown
attributes:
value: |
🛠 **Recipe Field Tips**
- `version` must be "1.0.0" for now
- `id` should be lowercase, hyphenated, and unique (e.g. `my-awesome-recipe`)
- `title` is the display name of your recipe
- `description` should clearly explain what the recipe does
- `instructions` are specific steps goose should follow — supports template variables like `{{ variable_name }}`
- `prompt` is the first thing goose sees when the recipe is launched
- `parameters` should include required or optional inputs — optional ones must have `default`
- `extensions` must follow the full format with `type`, `cmd`, `args`, `timeout`, etc.
- `activities` describe the main actions the recipe performs

View file

@ -2,7 +2,7 @@
## Test before Release
1. Close and reopen this PR to trigger CI and produce the Desktop bundle for testing. Reason: workflows don't run on PRs opened by `GITHUB_TOKEN` ([docs](https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow)).
1. Approve the workflows for this PR (scroll to bottom) to trigger CI and produce the Desktop bundle for testing.
2. Make sure all check workflows pass.
3. Install the Desktop bundle from the download links (posted as a comment below when it is ready).
4. Complete the goose Release Manual Testing Checklist (posted as a comment below).

View file

@ -40,7 +40,7 @@
## Project-Specific Context
- This is a Rust project using cargo workspaces
- Core crates: `goose` (agent logic), `goose-cli` (CLI), `goose-server` (backend), `goose-mcp` (MCP servers)
- Core crates: `goose` (agent logic and ACP server), `goose-cli` (CLI), `goose-mcp` (MCP servers)
- Error handling: Use `anyhow::Result`, not `unwrap()` in production code
- Async runtime: tokio
- MCP protocol implementations require extra scrutiny
@ -56,7 +56,6 @@
- `cargo fmt --check` - Code formatting (rustfmt)
- `cargo test --jobs 2` - All tests
- `cargo clippy --all-targets -- -D warnings` - Linting (clippy)
- `just check-openapi-schema` - OpenAPI schema validation
**Desktop app checks:**
- `pnpm install --frozen-lockfile` - Fresh dependency install (in `ui/desktop/`)

View file

@ -1,23 +0,0 @@
name: Close inactive issues
on:
schedule:
- cron: "30 1 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
days-before-issue-stale: 60
days-before-issue-close: 21
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
any-of-labels: "bug,Bug"
repo-token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -4,7 +4,7 @@
# - canary.yml
#
# Platform Build Strategy:
# - Linux standard: Uses native Ubuntu 22.04 runners to keep glibc compatibility with Ubuntu 22.04 LTS
# - Linux standard (x86_64 + aarch64): Builds inside manylinux_2_28 container for glibc 2.28+ compat
# - Linux Vulkan: Uses native Ubuntu 24.04 runners for newer Vulkan headers/tooling
# - Linux musl: Uses native Ubuntu 22.04 runners with reduced features for musl compatibility
# - macOS: Uses native macOS runners for each architecture
@ -27,6 +27,7 @@ jobs:
build-cli:
name: Build CLI
runs-on: ${{ matrix.build-on }}
container: ${{ matrix.container }}
env:
MACOSX_DEPLOYMENT_TARGET: "12.0"
strategy:
@ -37,11 +38,15 @@ jobs:
architecture: x86_64
target-suffix: unknown-linux-gnu
build-on: ubuntu-22.04
# Pinned by digest for reproducible builds; bump explicitly when newer manylinux_2_28 images ship.
container: quay.io/pypa/manylinux_2_28_x86_64@sha256:441c35fdc6ee809ff9260894f8468ab4fea8c15dc880f8700a3f81b7922c1cda
variant: standard
- platform: linux
architecture: aarch64
target-suffix: unknown-linux-gnu
build-on: ubuntu-22.04-arm
# Pinned by digest for reproducible builds; bump explicitly when newer manylinux_2_28 images ship.
container: quay.io/pypa/manylinux_2_28_aarch64@sha256:8b5f2b4e8c072ae5aefeb659f22c03e1ff46e6a82f154b6c904b106c87e65ff7
variant: standard
- platform: linux
architecture: x86_64
@ -81,12 +86,12 @@ jobs:
- platform: windows
architecture: x86_64
target-suffix: pc-windows-msvc
build-on: windows-latest
build-on: windows-2022
variant: cuda
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.ref }}
@ -97,8 +102,8 @@ jobs:
sed -i.bak 's/^version = ".*"/version = "'${{ inputs.version }}'"/' Cargo.toml
rm -f Cargo.toml.bak
- name: Install Linux build dependencies
if: matrix.platform == 'linux'
- name: Install Linux build dependencies (host runner)
if: matrix.platform == 'linux' && matrix.container == ''
run: |
sudo apt-get update
sudo apt-get install -y \
@ -119,11 +124,28 @@ jobs:
sudo apt-get install -y musl-tools
fi
- name: Install Linux build dependencies (manylinux container)
if: matrix.platform == 'linux' && matrix.container != ''
run: |
# perl-core provides FindBin, File::Compare, etc. that openssl-sys's
# vendored openssl build needs; in AlmaLinux 8 these aren't standalone packages.
# clang provides libclang.so for bindgen (used by llama-cpp-sys-2).
# Defensive: avoid actions/checkout falling back to a tarball download if base image changes.
dnf install -y --setopt=install_weak_deps=False \
openssl-devel \
dbus-devel \
libxcb-devel \
cmake \
perl-core \
clang \
git \
tar
- name: Cache Cargo artifacts (Linux/macOS)
if: matrix.platform != 'windows'
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: ${{ matrix.architecture }}-${{ matrix.target-suffix }}-${{ matrix.build-on }}-native-macos-deployment-target-12
key: ${{ matrix.architecture }}-${{ matrix.target-suffix }}-${{ matrix.build-on }}-${{ matrix.container || 'native' }}-macos-deployment-target-12
- name: Cache Cargo artifacts (Windows)
if: matrix.platform == 'windows'
@ -131,8 +153,8 @@ jobs:
with:
key: windows-msvc-cli-${{ matrix.variant }}
- name: Build CLI (Linux/macOS)
if: matrix.platform != 'windows'
- name: Build CLI (Linux/macOS host runner)
if: matrix.platform != 'windows' && matrix.container == ''
env:
RUST_LOG: debug
RUST_BACKTRACE: 1
@ -157,6 +179,27 @@ jobs:
cargo build --release --target ${TARGET} -p goose-cli "${FEATURE_ARGS[@]}"
fi
- name: Build CLI (manylinux container)
if: matrix.platform == 'linux' && matrix.container != ''
env:
RUST_BACKTRACE: 1
run: |
# Hermit's tool cache is host-runner-scoped; inside the container we
# bootstrap rustup directly and let rust-toolchain.toml pin the channel.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain none --profile minimal --no-modify-path
export PATH="$HOME/.cargo/bin:$PATH"
TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}"
RUST_CHANNEL=$(grep '^channel' rust-toolchain.toml | cut -d'"' -f2)
if [ -z "$RUST_CHANNEL" ]; then
echo "Could not parse channel from rust-toolchain.toml" >&2
exit 1
fi
rustup toolchain install "$RUST_CHANNEL" --profile minimal \
--component rustc,cargo --target "$TARGET"
rustup show
cargo build --release --target "$TARGET" -p goose-cli
- name: Setup Rust (Windows)
if: matrix.platform == 'windows'
shell: bash
@ -215,7 +258,10 @@ jobs:
- name: Package CLI (Linux/macOS)
if: matrix.platform != 'windows'
run: |
source ./bin/activate-hermit
# Hermit isn't installed in the manylinux container; tar is all this step needs.
if [ "${{ matrix.container }}" = '' ]; then
source ./bin/activate-hermit
fi
export TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}"
export VARIANT_SUFFIX=""
if [ "${{ matrix.variant }}" = "vulkan" ]; then
@ -253,7 +299,7 @@ jobs:
echo "ARTIFACT_ZIP=target/${TARGET}/release/goose-${TARGET}${VARIANT_SUFFIX}.zip" >> $GITHUB_ENV
- name: Upload CLI artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: goose-${{ matrix.architecture }}-${{ matrix.target-suffix }}${{ matrix.variant != 'standard' && matrix.variant != 'musl' && format('-{0}', matrix.variant) || '' }}
path: |

View file

@ -7,6 +7,8 @@ on:
- Release
- Canary
types: [completed]
branches-ignore:
- "release/**"
jobs:
@ -34,4 +36,4 @@ jobs:
"color": 15158332
}]
}' \
"$DISCORD_WEBHOOK_URL"
"$DISCORD_WEBHOOK_URL"

View file

@ -49,7 +49,7 @@ jobs:
run: df -h
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Only pass ref if it's explicitly set, otherwise let checkout action use its default behavior
ref: ${{ inputs.ref != '' && inputs.ref || '' }}
@ -73,11 +73,11 @@ jobs:
key: intel-macos-deployment-target-12
- name: Build goose-server for Intel macOS (x86_64)
- name: Build desktop backend for Intel macOS (x86_64)
run: |
source ./bin/activate-hermit
rustup target add x86_64-apple-darwin
cargo build --release -p goose-server --target x86_64-apple-darwin
cargo build --release -p goose-cli --bin goose --target x86_64-apple-darwin
@ -95,12 +95,16 @@ jobs:
# Check disk space after cleanup
df -h
- name: Copy binaries into Electron folder
- name: Copy backend binary into Electron folder
run: |
cp target/x86_64-apple-darwin/release/goosed ui/desktop/src/bin/goosed
mkdir -p ui/desktop/src/bin
rm -f ui/desktop/src/bin/goose
cp target/x86_64-apple-darwin/release/goose ui/desktop/src/bin/goose
chmod +x ui/desktop/src/bin/goose
ls -la ui/desktop/src/bin/
- name: Cache pnpm dependencies
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
path: |
ui/desktop/node_modules
@ -152,6 +156,10 @@ jobs:
fi
working-directory: ui/desktop
- name: Verify macOS updater resources
run: node scripts/verify-mac-update-resources.js "out/Goose-darwin-x64/Goose.app"
working-directory: ui/desktop
- name: Clean up signing keychain
if: always()
run: |
@ -168,7 +176,7 @@ jobs:
df -h
- name: Upload Desktop artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Goose-darwin-x64
path: ui/desktop/out/Goose-darwin-x64/Goose_intel_mac.zip

View file

@ -39,7 +39,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.ref || inputs.branch }}
@ -104,6 +104,11 @@ jobs:
sudo apt-get install -y libasound2
fi
- name: Configure RPM packaging
run: |
echo '%_build_id_links none' > ~/.rpmmacros
test "$(rpm --eval '%{_build_id_links}')" = "none"
- name: Setup Flatpak Runtimes
run: |
flatpak remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo
@ -119,7 +124,7 @@ jobs:
with:
key: linux-${{ matrix.build-on }}-${{ matrix.variant }}
- name: Build goosed binary
- name: Build desktop backend binary
env:
RUST_LOG: debug
RUST_BACKTRACE: 1
@ -132,19 +137,25 @@ jobs:
FEATURE_ARGS=(--features vulkan)
fi
cargo build --release --target ${TARGET} -p goose-server "${FEATURE_ARGS[@]}"
cargo build --release --target ${TARGET} -p goose-cli --bin goose "${FEATURE_ARGS[@]}"
- name: Copy binaries into Electron folder
- name: Copy backend binary into Electron folder
run: |
echo "Copying binaries to ui/desktop/src/bin/"
echo "Copying backend binary to ui/desktop/src/bin/"
export TARGET="x86_64-unknown-linux-gnu"
mkdir -p ui/desktop/src/bin
cp target/$TARGET/release/goosed ui/desktop/src/bin/
chmod +x ui/desktop/src/bin/goosed
rm -f ui/desktop/src/bin/goose
cp target/$TARGET/release/goose ui/desktop/src/bin/
chmod +x ui/desktop/src/bin/goose
ls -la ui/desktop/src/bin/
- name: Free Rust build artifacts before packaging
run: |
source ./bin/activate-hermit
cargo clean
- name: Cache pnpm dependencies
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
path: |
ui/desktop/node_modules
@ -181,6 +192,14 @@ jobs:
done
fi
for package in out/make/rpm/x64/*.rpm; do
package_contents="$(rpm -qlp "$package")"
if grep -qE '^/usr/lib/\.build-id(/|$)' <<< "$package_contents"; then
echo "::error file=$package::RPM contains build-id links"
exit 1
fi
done
echo "Build completed. Checking output..."
ls -la out/
find out/ -name "*.deb" -o -name "*.rpm" -o -name "*.flatpak" | head -10
@ -198,7 +217,7 @@ jobs:
- name: Upload .deb package
if: matrix.variant == 'standard'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Goose-linux-x64-deb
path: ui/desktop/out/make/deb/x64/*.deb
@ -206,7 +225,7 @@ jobs:
- name: Upload .deb package (Vulkan)
if: matrix.variant == 'vulkan'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Goose-linux-x64-vulkan-deb
path: ui/desktop/out/make/deb/x64/*-vulkan.deb
@ -214,7 +233,7 @@ jobs:
- name: Upload .rpm package
if: matrix.variant == 'standard'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Goose-linux-x64-rpm
path: ui/desktop/out/make/rpm/x64/*.rpm
@ -222,7 +241,7 @@ jobs:
- name: Upload .rpm package (Vulkan)
if: matrix.variant == 'vulkan'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Goose-linux-x64-vulkan-rpm
path: ui/desktop/out/make/rpm/x64/*-vulkan.rpm
@ -230,7 +249,7 @@ jobs:
- name: Upload .flatpak package
if: matrix.variant == 'standard'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Goose-linux-x64-flatpak
path: ui/desktop/out/make/flatpak/x86_64/*.flatpak
@ -238,7 +257,7 @@ jobs:
- name: Upload .flatpak package (Vulkan)
if: matrix.variant == 'vulkan'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Goose-linux-x64-vulkan-flatpak
path: ui/desktop/out/make/flatpak/x86_64/*-vulkan.flatpak

View file

@ -44,16 +44,16 @@ permissions:
jobs:
build-desktop-windows:
name: Build Desktop (Windows)
runs-on: windows-latest
runs-on: ${{ inputs.windows_variant == 'cuda' && 'windows-2022' || 'windows-latest' }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.ref != '' && inputs.ref || '' }}
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24.10.0
@ -61,7 +61,7 @@ jobs:
run: npm install -g pnpm@10.30.3
- name: Cache node_modules
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
path: |
node_modules
@ -113,28 +113,33 @@ jobs:
env:
CUDA_COMPUTE_CAP: ${{ inputs.windows_variant == 'cuda' && '80' || '' }}
run: |
Write-Output "Building Windows executable..."
if ("${{ inputs.windows_variant }}" -eq "cuda") {
cargo build --release --target x86_64-pc-windows-msvc -p goose-server --features cuda
$isCuda = "${{ inputs.windows_variant }}" -eq "cuda"
Write-Output "Building Windows ACP backend"
if ($isCuda) {
cargo build --release --target x86_64-pc-windows-msvc -p goose-cli --bin goose --features cuda
} else {
cargo build --release --target x86_64-pc-windows-msvc -p goose-server
cargo build --release --target x86_64-pc-windows-msvc -p goose-cli --bin goose
}
$binaryPath = "./target/x86_64-pc-windows-msvc/release/goose.exe"
# Verify build succeeded
if (-not (Test-Path "./target/x86_64-pc-windows-msvc/release/goosed.exe")) {
Write-Error "Windows binary not found."
if (-not (Test-Path $binaryPath)) {
Write-Error "Windows backend binary not found: $binaryPath"
Get-ChildItem ./target/x86_64-pc-windows-msvc/release/ -ErrorAction SilentlyContinue
exit 1
}
Write-Output "Windows binary found."
Get-Item ./target/x86_64-pc-windows-msvc/release/goosed.exe
Write-Output "Windows backend binary found."
Get-Item $binaryPath
- name: Prepare Windows binary
shell: bash
run: |
if [ ! -f "./target/x86_64-pc-windows-msvc/release/goosed.exe" ]; then
echo "Windows binary not found."
BACKEND_BINARY="./target/x86_64-pc-windows-msvc/release/goose.exe"
if [ ! -f "$BACKEND_BINARY" ]; then
echo "Windows backend binary not found: $BACKEND_BINARY"
exit 1
fi
@ -142,13 +147,14 @@ jobs:
rm -rf ./ui/desktop/src/bin
mkdir -p ./ui/desktop/src/bin
echo "Copying Windows binary..."
cp -f ./target/x86_64-pc-windows-msvc/release/goosed.exe ./ui/desktop/src/bin/
echo "Copying Windows backend binary..."
cp -f "$BACKEND_BINARY" ./ui/desktop/src/bin/
if [ -d "./ui/desktop/src/platform/windows/bin" ]; then
echo "Copying Windows platform files..."
for file in ./ui/desktop/src/platform/windows/bin/*.{exe,dll,cmd}; do
if [ -f "$file" ] && [ "$(basename "$file")" != "goosed.exe" ]; then
filename="$(basename "$file")"
if [ -f "$file" ] && [ "$filename" != "goose.exe" ]; then
cp -f "$file" ./ui/desktop/src/bin/
fi
done
@ -195,7 +201,7 @@ jobs:
ls -la ./dist-windows/resources/bin/
- name: Upload unsigned distribution
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: windows-unsigned${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
path: ui/desktop/dist-windows/
@ -229,14 +235,14 @@ jobs:
certificate-profile-name: ${{ secrets.AZURE_CERTIFICATE_PROFILE_NAME }}
files: |
${{ github.workspace }}/dist-windows/Goose.exe
${{ github.workspace }}/dist-windows/resources/bin/goosed.exe
${{ github.workspace }}/dist-windows/resources/bin/goose.exe
- name: Verify signed executables
shell: pwsh
run: |
$files = @(
"dist-windows/Goose.exe",
"dist-windows/resources/bin/goosed.exe"
"dist-windows/resources/bin/goose.exe"
)
foreach ($file in $files) {
Write-Output "Verifying signature: $file"
@ -255,7 +261,7 @@ jobs:
7z a -tzip "${ZIP_NAME}.zip" dist-windows/
- name: Upload signed Windows build
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Goose-win32-x64${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
path: Goose-win32-x64${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}.zip
@ -284,7 +290,7 @@ jobs:
7z a -tzip "${ZIP_NAME}.zip" dist-windows/
- name: Upload Windows build
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Goose-win32-x64${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}
path: Goose-win32-x64${{ inputs.windows_variant == 'cuda' && '-cuda' || '' }}.zip

View file

@ -77,7 +77,7 @@ jobs:
run: df -h
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Only pass ref if it's explicitly set, otherwise let checkout action use its default behavior
ref: ${{ inputs.ref != '' && inputs.ref || '' }}
@ -118,8 +118,10 @@ jobs:
key: macos-deployment-target-12
# Build the project
- name: Build goosed
run: source ./bin/activate-hermit && cargo build --release -p goose-server
- name: Build desktop backend
run: |
source ./bin/activate-hermit
cargo build --release -p goose-cli --bin goose
# Post-build cleanup to free space
- name: Post-build cleanup
@ -134,12 +136,16 @@ jobs:
# Check disk space after cleanup
df -h
- name: Copy binaries into Electron folder
- name: Copy backend binary into Electron folder
run: |
cp target/release/goosed ui/desktop/src/bin/goosed
mkdir -p ui/desktop/src/bin
rm -f ui/desktop/src/bin/goose
cp target/release/goose ui/desktop/src/bin/goose
chmod +x ui/desktop/src/bin/goose
ls -la ui/desktop/src/bin/
- name: Cache pnpm dependencies
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
path: |
ui/desktop/node_modules
@ -184,6 +190,10 @@ jobs:
fi
working-directory: ui/desktop
- name: Verify macOS updater resources
run: node scripts/verify-mac-update-resources.js "out/Goose-darwin-arm64/Goose.app"
working-directory: ui/desktop
- name: Clean up signing keychain
if: always()
run: |
@ -201,7 +211,7 @@ jobs:
- name: Upload Desktop artifact
id: upload-app-bundle
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Goose-darwin-arm64
path: ui/desktop/out/Goose-darwin-arm64/Goose.zip

View file

@ -33,7 +33,7 @@ jobs:
version: ${{ steps.set-version.outputs.version }}
steps:
# checkout code so we can read the Cargo.toml
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Generate a canary version
id: set-version
run: |
@ -60,14 +60,14 @@ jobs:
runs-on: ubuntu-latest
needs: [build-cli]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: download_cli.sh
path: download_cli.sh
# ------------------------------------------------------------
# 4) Bundle Desktop App (macOS only) - builds goosed and Electron app
# 4) Bundle Desktop App (macOS only)
# ------------------------------------------------------------
bundle-desktop:
needs: [prepare-version]
@ -80,7 +80,7 @@ jobs:
signing: false
# ------------------------------------------------------------
# 5) Bundle Desktop App (macOS Intel) - builds goosed and Electron app
# 5) Bundle Desktop App (macOS Intel)
# ------------------------------------------------------------
bundle-desktop-intel:
needs: [prepare-version]
@ -93,7 +93,7 @@ jobs:
signing: false
# ------------------------------------------------------------
# 6) Bundle Desktop App (Linux) - builds goosed and Electron app
# 6) Bundle Desktop App (Linux)
# ------------------------------------------------------------
bundle-desktop-linux:
needs: [prepare-version]
@ -102,7 +102,7 @@ jobs:
version: ${{ needs.prepare-version.outputs.version }}
# ------------------------------------------------------------
# 6) Bundle Desktop App (Windows) - builds goosed and Electron app
# 6) Bundle Desktop App (Windows)
# ------------------------------------------------------------
bundle-desktop-windows:
needs: [prepare-version]
@ -138,7 +138,7 @@ jobs:
merge-multiple: true
- name: Attest build provenance
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: |
goose-*.tar.bz2

View file

@ -22,8 +22,8 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7.0.0
# https://github.com/EmbarkStudios/cargo-deny-action v2.0.15
- uses: EmbarkStudios/cargo-deny-action@a531616d8ce3b9177443e48a1159bc945a099823
- uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe
with:
command: check advisories

View file

@ -20,5 +20,5 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: bnjbvr/cargo-machete@ac30a525c0a8d163a92d727b3ff079ee3f6ecb08 # v0.9.2

View file

@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
if: startsWith(github.head_ref, 'release/')
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.head_ref }}
fetch-depth: 0

View file

@ -20,7 +20,7 @@ jobs:
code: ${{ steps.filter.outputs.code }}
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check for file changes
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # pin@v3
@ -39,9 +39,9 @@ jobs:
if: needs.changes.outputs.code == 'true' || github.event_name != 'pull_request'
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 # v1.16.1
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
- name: Run cargo fmt
run: cargo fmt --check
@ -53,9 +53,9 @@ jobs:
if: needs.changes.outputs.code == 'true' || github.event_name != 'pull_request'
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 # v1.16.1
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
- name: Install Dependencies
run: |
@ -82,7 +82,7 @@ jobs:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Cache Cargo artifacts
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
@ -106,7 +106,7 @@ jobs:
needs: changes
if: needs.changes.outputs.code == 'true' || github.event_name != 'pull_request'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Read MSRV from Cargo.toml
id: msrv
@ -119,7 +119,7 @@ jobs:
echo "msrv=$msrv" >> "$GITHUB_OUTPUT"
echo "MSRV: $msrv"
- uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 # v1
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
with:
toolchain: ${{ steps.msrv.outputs.msrv }}
@ -143,9 +143,9 @@ jobs:
needs: changes
if: needs.changes.outputs.code == 'true' || github.event_name != 'pull_request'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 # v1.16.1
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
@ -165,9 +165,9 @@ jobs:
if: needs.changes.outputs.code == 'true' || github.event_name != 'pull_request'
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 # v1.16.1
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
- name: Install Dependencies
run: |
@ -183,17 +183,18 @@ jobs:
cd ui/desktop && pnpm install --frozen-lockfile
cd ../sdk && pnpm install --frozen-lockfile
- name: Check OpenAPI Schema is Up-to-Date
run: |
source ./bin/activate-hermit
hermit uninstall rustup
just check-openapi-schema
- name: Check ACP Schema is Up-to-Date
run: |
source ./bin/activate-hermit
just check-acp-schema
- name: Test ACP Client SDK
run: |
source ./bin/activate-hermit
cd ui/sdk
pnpm test
pnpm run typecheck:test
desktop-lint:
name: Test and Lint Electron Desktop App
runs-on: macos-latest
@ -201,7 +202,7 @@ jobs:
if: needs.changes.outputs.code == 'true' || github.event_name != 'pull_request'
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
# Temporarily disabled due to GitHub Actions bug on macOS runners
# https://github.com/actions/runner-images/issues/13341

View file

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Extract version from tag
id: version

View file

@ -64,7 +64,7 @@ jobs:
OIDC_TOKEN: ${{ steps.oidc.outputs.token }}
- name: Upload review inputs
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: code-review-inputs
path: /tmp/code-review/
@ -84,7 +84,7 @@ jobs:
steps:
- name: Checkout base branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install tools
run: |
@ -119,7 +119,7 @@ jobs:
- name: Upload review output
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: code-review-output
path: |

View file

@ -18,13 +18,13 @@ jobs:
github.event.pull_request.merged == true &&
startsWith(github.event.pull_request.head.ref, 'version-bump/')
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
fetch-depth: 0
- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- name: Extract version and base branch
env:

View file

@ -25,64 +25,77 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: main
fetch-depth: 0
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: main
fetch-depth: 0
- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- name: install dependencies
run: |
sudo apt update -y
sudo apt install -y libdbus-1-dev gnome-keyring libxcb1-dev
- name: install dependencies
run: |
sudo apt update -y
sudo apt install -y libdbus-1-dev gnome-keyring libxcb1-dev
- name: Compute version and create bump branch
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
TETRATE_API_KEY: ${{ secrets.TETRATE_API_KEY }}
run: |
VERSION=$(just get-next-minor-version)
echo "version=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION (minor bump on main)"
- name: Compute version and create bump branch
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
TETRATE_API_KEY: ${{ secrets.TETRATE_API_KEY }}
run: |
VERSION=$(just get-next-minor-version)
echo "version=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION (minor bump on main)"
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
BRANCH_NAME="version-bump/$VERSION"
git switch -c "$BRANCH_NAME"
BRANCH_NAME="version-bump/$VERSION"
git switch -C "$BRANCH_NAME"
just prepare-release $VERSION
just prepare-release $VERSION
echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV
echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV
- name: Push bump branch
run: |
git push origin "${{ env.branch_name }}"
- name: Push bump branch
run: |
git push --force-with-lease origin "${{ env.branch_name }}"
- name: Create Pull Request
run: |
PR_BODY=$(cat <<'EOF'
Bumps version to **${{ env.version }}**.
- name: Create Pull Request if needed
run: |
EXISTING_PR_URL=$(gh pr list \
--base main \
--head "${{ env.branch_name }}" \
--state open \
--json url \
--jq '.[0].url // empty')
**Please follow these steps:**
if [ -n "$EXISTING_PR_URL" ]; then
echo "Existing PR found: $EXISTING_PR_URL"
echo "pr_url=$EXISTING_PR_URL" >> $GITHUB_ENV
exit 0
fi
1. Close and reopen this PR to trigger CI checks. Reason: workflows don't run on PRs opened by `GITHUB_TOKEN` ([docs](https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow)).
2. Review and resolve any merge conflicts.
3. Approve and merge this PR.
4. The `release/${{ env.version }}` PR will be created automatically.
EOF
)
PR_URL=$(gh pr create \
-B main \
-H "${{ env.branch_name }}" \
--title "chore(release): bump version to ${{ env.version }} (minor)" \
--body "$PR_BODY")
echo "pr_url=$PR_URL" >> $GITHUB_ENV
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_BODY=$(cat <<'EOF'
Bumps version to **${{ env.version }}**.
**Please follow these steps:**
1. Approve workflows for this PR to trigger CI checks.
2. Review and resolve any merge conflicts.
3. Approve and merge this PR.
4. The `release/${{ env.version }}` PR will be created automatically.
EOF
)
PR_URL=$(gh pr create \
-B main \
-H "${{ env.branch_name }}" \
--title "chore(release): bump version to ${{ env.version }} (minor)" \
--body "$PR_BODY")
echo "pr_url=$PR_URL" >> $GITHUB_ENV
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -24,15 +24,15 @@ jobs:
steps:
- name: Checkout the branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20
- name: Cache Node.js modules (documentation)
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
path: ./documentation/node_modules
key: ${{ runner.os }}-documentation-${{ hashFiles('./documentation/package-lock.json') }}
@ -58,10 +58,10 @@ jobs:
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
- name: Upload GitHub Pages artifact
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: documentation/build
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0

View file

@ -43,7 +43,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0 # Fetch all history for version comparison
fetch-tags: true # Fetch all tags so we can checkout version tags
@ -63,7 +63,7 @@ jobs:
sudo apt-get install -y jq ripgrep
- name: Set up Rust
uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 # v1.16.1
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
with:
toolchain: stable
@ -73,7 +73,7 @@ jobs:
python-version: '3.11'
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20'
@ -191,7 +191,7 @@ jobs:
- name: Upload automation outputs
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cli-docs-update-${{ steps.versions.outputs.old_version }}-to-${{ steps.versions.outputs.new_version }}
path: |

View file

@ -1,292 +0,0 @@
# Automatically updates the Recipe Reference Guide when recipe struct fields,
# validation rules, or schema constraints change between releases.
#
# Triggers: Manual (for testing) or on release (production)
# Testing: Use dry_run mode to review outputs without creating PRs
# See: documentation/automation/recipe-schema-tracking/TESTING.md
name: Update Recipe Documentation
on:
workflow_dispatch: # Manual trigger for testing
inputs:
old_version:
description: 'Previous version (e.g., v1.14.0). Leave empty to auto-detect.'
required: false
type: string
new_version:
description: 'New version (e.g., v1.15.0). Leave empty to use HEAD.'
required: false
type: string
dry_run:
description: 'Dry run mode - generate files but do not create PR'
required: false
type: boolean
default: true
release:
types: [published]
permissions:
contents: write # Create branches and commit files
pull-requests: write # Create PRs
jobs:
update-docs:
name: Update Recipe Documentation
runs-on: ubuntu-latest
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0 # Fetch all history for version comparison
fetch-tags: true # Fetch all tags so we can checkout version tags
- name: Fetch upstream tags (for forks)
if: github.repository != 'aaif-goose/goose'
run: |
# Add upstream remote and fetch tags (only needed when testing in forks)
git remote add upstream https://github.com/aaif-goose/goose.git || git remote set-url upstream https://github.com/aaif-goose/goose.git
git fetch upstream --tags --force
echo "✅ Fetched tags from upstream (fork mode)"
echo "Total tags available: $(git tag | wc -l)"
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y jq ripgrep
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '20'
- name: Install goose CLI
run: |
mkdir -p /home/runner/.local/bin
curl -fsSL https://github.com/aaif-goose/goose/releases/download/stable/download_cli.sh \
| CONFIGURE=false GOOSE_BIN_DIR=/home/runner/.local/bin bash
echo "/home/runner/.local/bin" >> $GITHUB_PATH
goose --version
- name: Configure goose for CI
env:
GOOSE_PROVIDER: ${{ vars.GOOSE_PROVIDER || 'openai' }}
GOOSE_MODEL: ${{ vars.GOOSE_MODEL || 'gpt-4o' }}
run: |
mkdir -p ~/.config/goose
cat <<EOF > ~/.config/goose/config.yaml
GOOSE_PROVIDER: $GOOSE_PROVIDER
GOOSE_MODEL: $GOOSE_MODEL
keyring: false
EOF
echo "✅ Created goose config:"
cat ~/.config/goose/config.yaml
- name: Determine versions to compare
id: versions
env:
GH_TOKEN: ${{ github.token }}
INPUT_OLD_VERSION: ${{ github.event.inputs.old_version }}
INPUT_NEW_VERSION: ${{ github.event.inputs.new_version }}
EVENT_NAME: ${{ github.event_name }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
get_previous_release() {
gh release list --limit 2 --json tagName --jq '.[].tagName' | sed -n '2p'
}
if [ -n "$INPUT_OLD_VERSION" ]; then
OLD_VERSION="$INPUT_OLD_VERSION"
else
OLD_VERSION=$(get_previous_release)
fi
if [ -n "$INPUT_NEW_VERSION" ]; then
NEW_VERSION="$INPUT_NEW_VERSION"
elif [ "$EVENT_NAME" = "release" ]; then
NEW_VERSION="$RELEASE_TAG"
else
NEW_VERSION="HEAD" # For testing unreleased changes
fi
if [ -z "$OLD_VERSION" ] || [ -z "$NEW_VERSION" ]; then
echo "Error: Could not determine versions to compare"
exit 1
fi
echo "old_version=$OLD_VERSION" >> $GITHUB_OUTPUT
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "OLD_VERSION=$OLD_VERSION" >> $GITHUB_ENV
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV
echo "✅ Comparing $OLD_VERSION → $NEW_VERSION"
- name: Extract and compare schemas
id: extract
timeout-minutes: 15
working-directory: documentation/automation/recipe-schema-tracking
env:
GOOSE_REPO: ${{ github.workspace }}
run: |
set -o pipefail # Ensure pipeline failures are caught
mkdir -p output
./scripts/run-pipeline.sh "$OLD_VERSION" "$NEW_VERSION" 2>&1 | tee output/pipeline.log
HAS_CHANGES=$(jq -r '.has_changes' output/validation-changes.json)
echo "has_changes=$HAS_CHANGES" >> $GITHUB_OUTPUT
if [ "$HAS_CHANGES" = "false" ]; then
echo "✅ No changes detected"
else
echo "✅ Changes detected"
fi
- name: Update recipe-reference.md (AI synthesis)
if: steps.extract.outputs.has_changes == 'true'
timeout-minutes: 10
working-directory: documentation/automation/recipe-schema-tracking/output
env:
RECIPE_REF_PATH: ${{ github.workspace }}/documentation/docs/guides/recipes/recipe-reference.md
run: |
echo "🔍 Environment diagnostics:"
echo " GOOSE_PROVIDER: $GOOSE_PROVIDER"
echo " GOOSE_MODEL: $GOOSE_MODEL"
echo " OPENAI_API_KEY: ${OPENAI_API_KEY:0:8}..." # Show first 8 chars only
echo " RECIPE_REF_PATH: $RECIPE_REF_PATH"
echo " HOME: $HOME"
echo " PATH: $PATH"
echo ""
echo "📁 Goose config file:"
cat ~/.config/goose/config.yaml || echo "Config file not found!"
echo ""
echo "📁 Current directory:"
pwd
ls -la
echo ""
echo "🤖 Step 1: Running validation changes synthesis..."
goose run --recipe ../recipes/synthesize-validation-changes.yaml
echo ""
echo "🤖 Step 2: Applying changes to recipe-reference.md..."
goose run --recipe ../recipes/update-recipe-reference.yaml
- name: Upload automation outputs
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: recipe-docs-update-${{ steps.versions.outputs.old_version }}-to-${{ steps.versions.outputs.new_version }}
path: |
documentation/automation/recipe-schema-tracking/output/*.json
documentation/automation/recipe-schema-tracking/output/*.md
documentation/automation/recipe-schema-tracking/output/*.log
retention-days: 30
- name: Create Pull Request
if: |
steps.extract.outputs.has_changes == 'true' &&
(github.event.inputs.dry_run != 'true' || github.event_name == 'release')
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
branch: docs/auto-recipe-reference-${{ steps.versions.outputs.new_version }}
delete-branch: true
commit-message: |
docs: Update recipe reference for ${{ steps.versions.outputs.new_version }}
Automated update based on schema changes between ${{ steps.versions.outputs.old_version }} and ${{ steps.versions.outputs.new_version }}.
title: "docs: Update Recipe Reference Guide for ${{ steps.versions.outputs.new_version }}"
body: |
## Summary
This PR updates the Recipe Reference Guide based on schema and validation changes detected between **${{ steps.versions.outputs.old_version }}** and **${{ steps.versions.outputs.new_version }}**.
### Type of Change
- [x] Documentation
### AI Assistance
- [x] This PR was created or reviewed with AI assistance
#### 🤖 Automation Details
- **Workflow**: `docs-update-recipe-ref.yml`
- **Triggered by**: ${{ github.event_name }}
- **Previous version**: ${{ steps.versions.outputs.old_version }}
- **New version**: ${{ steps.versions.outputs.new_version }}
#### 📋 Changes Detected
Review the workflow artifacts for detailed change analysis:
- `validation-changes.json` - Structured diff of changes
- `validation-changes.md` - Human-readable change documentation
- `update-summary.md` - Summary of documentation updates applied
Download artifacts from the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
### ✅ Review Checklist
- [ ] Verify all schema changes are accurately documented
- [ ] Check that examples are updated correctly
- [ ] Ensure validation rules are clearly explained
- [ ] Confirm no unintended changes were made
- [ ] Do changes require additional updates in this or other recipe topics?
### 🔗 Related
- Release: ${{ github.event.release.html_url || 'N/A' }}
---
*This PR was automatically generated by the Recipe Documentation Automation workflow.*
labels: |
documentation
automated
recipe-reference
- name: Workflow summary
if: always()
env:
OLD_VERSION: ${{ steps.versions.outputs.old_version }}
NEW_VERSION: ${{ steps.versions.outputs.new_version }}
HAS_CHANGES: ${{ steps.extract.outputs.has_changes }}
DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
run: |
echo "## 📊 Recipe Documentation Update Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version Comparison**: $OLD_VERSION → $NEW_VERSION" >> $GITHUB_STEP_SUMMARY
echo "**Changes Detected**: $HAS_CHANGES" >> $GITHUB_STEP_SUMMARY
echo "**Dry Run Mode**: $DRY_RUN" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$HAS_CHANGES" = "true" ]; then
echo "### ✅ Documentation Updated" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "The Recipe Reference Guide has been updated to reflect changes in $NEW_VERSION." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$DRY_RUN" = "true" ]; then
echo "**Note**: Running in dry-run mode - no PR was created. Review the artifacts to see the generated changes." >> $GITHUB_STEP_SUMMARY
else
echo "A pull request has been created with the documentation updates." >> $GITHUB_STEP_SUMMARY
fi
else
echo "### No Changes Needed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "No recipe schema or validation changes were detected between $OLD_VERSION and $NEW_VERSION." >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📦 Artifacts" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Download the workflow artifacts to review:" >> $GITHUB_STEP_SUMMARY
echo "- Extracted schemas and validation structures" >> $GITHUB_STEP_SUMMARY
echo "- Change detection results" >> $GITHUB_STEP_SUMMARY
echo "- Human-readable change documentation" >> $GITHUB_STEP_SUMMARY
echo "- Documentation update summary" >> $GITHUB_STEP_SUMMARY

View file

@ -30,7 +30,7 @@ env:
- Deletion is a feature: ask "can I delete code instead of adding?"
- Complete changes: update ALL usages when changing a type. No exceptions.
- When changing message types, update ALL provider format functions.
- When changing server routes, run `just generate-openapi`.
- When changing ACP request/response types, run `just generate-acp-types`.
- Your context degrades. The TODO is your memory. Update it after each step.
Types:
@ -150,7 +150,7 @@ jobs:
-d '{"content":"eyes"}'
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: main
fetch-depth: 1

View file

@ -241,7 +241,7 @@ jobs:
-d '{"content":"eyes"}'
- name: Checkout PR
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: refs/pull/${{ github.event.issue.number }}/head
fetch-depth: 1

View file

@ -15,6 +15,8 @@ on:
- Release
types:
- completed
branches-ignore:
- "release/**"
# Allow manual trigger for testing
workflow_dispatch:
@ -76,7 +78,7 @@ jobs:
generate-release-notes:
name: Generate Release Notes
runs-on: ubuntu-latest
# For workflow_run: only run if Release succeeded and tag is not 'stable'
# For workflow_run: release branch preflight builds are ignored by the trigger.
# For workflow_dispatch: only run if tag is not 'stable'
if: |
(github.event_name == 'workflow_dispatch' && inputs.tag != 'stable') ||
@ -113,7 +115,7 @@ jobs:
fi
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
fetch-tags: true

View file

@ -6,12 +6,61 @@ permissions:
on:
schedule:
- cron: '0 0 * * 2'
- cron: "0 0 * * 2"
workflow_dispatch:
push:
branches:
- main
jobs:
check-version-bump-pr:
if: github.repository == 'aaif-goose/goose' && github.event_name == 'push'
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.check.outputs.should_run }}
steps:
- name: Check for conflicted version bump PR
id: check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
for attempt in 1 2 3 4 5 6; do
CONFLICTING_PR_URLS=$(gh pr list \
--repo "$GITHUB_REPOSITORY" \
--base main \
--state open \
--search 'head:version-bump/' \
--json mergeStateStatus,url \
--jq '.[] | select(.mergeStateStatus == "DIRTY") | .url')
UNKNOWN_COUNT=$(gh pr list \
--repo "$GITHUB_REPOSITORY" \
--base main \
--state open \
--search 'head:version-bump/' \
--json mergeStateStatus \
--jq '[.[] | select(.mergeStateStatus == "UNKNOWN")] | length')
if [ -n "$CONFLICTING_PR_URLS" ] || [ "$UNKNOWN_COUNT" = "0" ]; then
break
fi
echo "Waiting for GitHub to compute PR mergeability (attempt $attempt)..."
sleep 10
done
if [ -n "$CONFLICTING_PR_URLS" ]; then
echo "Found conflicted version bump PR(s):"
echo "$CONFLICTING_PR_URLS"
echo "should_run=true" >> "$GITHUB_OUTPUT"
else
echo "No conflicted version bump PR found."
echo "should_run=false" >> "$GITHUB_OUTPUT"
fi
release:
if: github.repository == 'aaif-goose/goose'
needs: check-version-bump-pr
if: always() && github.repository == 'aaif-goose/goose' && (github.event_name != 'push' || needs.check-version-bump-pr.outputs.should_run == 'true')
uses: ./.github/workflows/create-version-bump-pr.yaml
secrets:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

View file

@ -19,13 +19,13 @@ jobs:
TARGET_BRANCH: ${{ inputs.target_branch }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.target_branch }}
fetch-depth: 0
- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- name: install dependencies
run: |

View file

@ -92,7 +92,7 @@ jobs:
- name: Checkout code
if: steps.security_check.outputs.authorized == 'true'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Get PR head SHA with gh
id: set_head_sha

View file

@ -45,7 +45,7 @@ jobs:
allowed_contexts: pull_request
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Get PR head SHA with gh
id: set_head_sha

View file

@ -48,7 +48,7 @@ jobs:
allowed_contexts: pull_request
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Get PR head SHA with gh
id: set_head_sha

View file

@ -30,7 +30,7 @@ jobs:
code: ${{ steps.filter.outputs.code }}
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.event.inputs.branch || github.ref }}
fetch-depth: 0
@ -51,11 +51,11 @@ jobs:
if: needs.changes.outputs.code == 'true' || github.event_name == 'workflow_dispatch'
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.event.inputs.branch || github.ref }}
- uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 # v1.16.1
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
- name: Install Dependencies
run: |
@ -67,29 +67,22 @@ jobs:
- name: Build Binary for Smoke Tests
run: |
cargo build --bin goose --bin goosed
cargo build --bin goose
- name: Upload goose binary
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: goose-binary
path: target/debug/goose
retention-days: 1
- name: Upload goosed binary
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: goosed-binary
path: target/debug/goosed
retention-days: 1
smoke-tests:
name: Smoke Tests
runs-on: ubuntu-latest
needs: build-binary
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.event.inputs.branch || github.ref }}
@ -103,7 +96,7 @@ jobs:
run: chmod +x target/debug/goose
- name: Set up Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: '22'
@ -142,7 +135,7 @@ jobs:
python-version: '3.12'
- name: Install uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- name: Run MCP Tests
env:
@ -177,7 +170,7 @@ jobs:
needs: build-binary
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.event.inputs.branch || github.ref }}
@ -220,7 +213,7 @@ jobs:
needs: build-binary
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.event.inputs.branch || github.ref }}
@ -239,7 +232,7 @@ jobs:
python-version: '3.12'
- name: Install uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- name: Run Compaction Tests
env:
@ -253,39 +246,3 @@ jobs:
mkdir -p $HOME/.local/share/goose/sessions
mkdir -p $HOME/.config/goose
bash scripts/test_compaction.sh
goosed-integration-tests:
name: goose server HTTP integration tests
runs-on: ubuntu-latest
needs: build-binary
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.inputs.branch || github.ref }}
- name: Download Binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: goosed-binary
path: target/debug
- name: Make Binary Executable
run: chmod +x target/debug/goosed
- name: Install Node.js Dependencies
run: source ../../bin/activate-hermit && pnpm install --frozen-lockfile
working-directory: ui/desktop
- name: Run Integration Tests
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GOOSED_BINARY: ../../target/debug/goosed
GOOSE_PROVIDER: anthropic
GOOSE_MODEL: claude-sonnet-4-5-20250929
SHELL: /bin/bash
SKIP_BUILD: 1
run: |
echo 'export PATH=/some/fake/path:$PATH' >> $HOME/.bash_profile
source ../../bin/activate-hermit && pnpm run test:integration:goosed
working-directory: ui/desktop

View file

@ -20,12 +20,16 @@ jobs:
permissions:
contents: read
pull-requests: write
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.PAGES_PR_PREVIEW_CF_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.PAGES_PR_PREVIEW_CF_API_TOKEN }}
CLOUDFLARE_PAGES_PROJECT_NAME: ${{ secrets.PAGES_PR_PREVIEW_CF_PAGES_PROJECT_NAME }}
steps:
- name: Checkout the branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js for docs build
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20
cache: npm
@ -47,17 +51,16 @@ jobs:
run: ./scripts/verify-build.sh
- name: Setup Node.js for Wrangler
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
if: env.CLOUDFLARE_API_TOKEN != ''
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 22
- name: Deploy preview to Cloudflare Pages
if: env.CLOUDFLARE_API_TOKEN != ''
id: cloudflare-pages
working-directory: ./documentation
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.PAGES_PR_PREVIEW_CF_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.PAGES_PR_PREVIEW_CF_API_TOKEN }}
CLOUDFLARE_PAGES_PROJECT_NAME: ${{ secrets.PAGES_PR_PREVIEW_CF_PAGES_PROJECT_NAME }}
PR_NUMBER: ${{ github.event.number }}
run: |
set -euo pipefail
@ -76,6 +79,7 @@ jobs:
echo "preview-url=$preview_url" >> "$GITHUB_OUTPUT"
- name: Comment preview URL
if: env.CLOUDFLARE_API_TOKEN != ''
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
PREVIEW_URL: ${{ steps.cloudflare-pages.outputs.preview-url }}

View file

@ -19,13 +19,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Log in to GitHub Container Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@ -33,7 +33,7 @@ jobs:
- name: Extract metadata
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ghcr.io/${{ github.repository_owner }}/ask-ai-bot
tags: |
@ -42,7 +42,7 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # pin@v7.2.0
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # pin@v7.3.0
with:
context: .
file: services/ask-ai-bot/Dockerfile

View file

@ -23,13 +23,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Log in to GitHub Container Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@ -37,7 +37,7 @@ jobs:
- name: Extract metadata
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ghcr.io/${{ github.repository_owner }}/goose
tags: |
@ -54,7 +54,7 @@ jobs:
- name: Build and push Docker image
id: docker-push
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # pin@v7.2.0
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # pin@v7.3.0
with:
context: .
push: true
@ -65,7 +65,7 @@ jobs:
platforms: linux/amd64,linux/arm64
- name: Attest Docker image
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-name: ghcr.io/${{ github.repository_owner }}/goose
subject-digest: ${{ steps.docker-push.outputs.digest }}

View file

@ -22,17 +22,17 @@ jobs:
needs: [build-cli]
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24.10.0'
registry-url: 'https://registry.npmjs.org'
always-auth: true
- name: Setup pnpm
uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: 10.30.3
@ -100,10 +100,7 @@ jobs:
cd ui/sdk
pnpm run build:ts
cd ../text
pnpm run build
- name: Compatibility check (TUI client ↔ goose binary)
- name: Compatibility check (SDK client ↔ goose binary)
env:
GOOSE_BINARY: ${{ github.workspace }}/ui/goose-binary/goose-binary-linux-x64/bin/goose
run: |
@ -111,7 +108,7 @@ jobs:
pnpm run check:compat
- name: Upload built packages
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: npm-packages
path: ui/
@ -135,7 +132,7 @@ jobs:
echo ""
echo "### npm Packages"
cd ui
for pkg in sdk text goose-binary/*/; do
for pkg in sdk goose-binary/*/; do
if [ -f "$pkg/package.json" ]; then
name=$(jq -r '.name' "$pkg/package.json")
version=$(jq -r '.version' "$pkg/package.json")
@ -160,14 +157,14 @@ jobs:
run: find ui/goose-binary -name 'goose' -type f -exec chmod +x {} +
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24.10.0'
registry-url: 'https://registry.npmjs.org'
always-auth: true
- name: Setup pnpm
uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: 10.30.3
@ -177,7 +174,7 @@ jobs:
# Publish each package individually so one failure doesn't abort the rest.
# Skips packages whose version is already published (409/403).
failed=0
for pkg in sdk goose-binary/*/ text; do
for pkg in sdk goose-binary/*/; do
if [ -f "$pkg/package.json" ]; then
name=$(jq -r '.name' "$pkg/package.json")
version=$(jq -r '.version' "$pkg/package.json")
@ -207,7 +204,7 @@ jobs:
{
echo "## 🚀 Published Packages"
echo ""
for pkg in sdk text goose-binary/*/; do
for pkg in sdk goose-binary/*/; do
if [ -f "$pkg/package.json" ]; then
name=$(jq -r '.name' "$pkg/package.json")
version=$(jq -r '.version' "$pkg/package.json")

118
.github/workflows/python-sdk-wheels.yml vendored Normal file
View file

@ -0,0 +1,118 @@
name: Python SDK Wheels
on:
workflow_dispatch:
inputs:
publish:
description: "Publish wheels to PyPI after building"
required: true
default: false
type: boolean
permissions:
contents: read
id-token: write
jobs:
build-wheels:
name: Build wheel (${{ matrix.name }})
runs-on: ${{ matrix.os }}
container: ${{ matrix.container || null }}
strategy:
fail-fast: false
matrix:
include:
- name: macos-arm64
os: macos-14
- name: macos-x86_64
os: macos-13
- name: linux-x86_64
os: ubuntu-latest
container: quay.io/pypa/manylinux_2_28_x86_64@sha256:441c35fdc6ee809ff9260894f8468ab4fea8c15dc880f8700a3f81b7922c1cda
- name: windows-x86_64
os: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Rust
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
enable-cache: true
- name: Install Linux tools
if: runner.os == 'Linux'
shell: bash
run: |
python3 -m pip install --upgrade pip
python3 -m pip install uv
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
- name: Install just
if: runner.os != 'Linux'
uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Cache Cargo artifacts
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Build wheel
shell: bash
run: just --justfile crates/goose-sdk/justfile python-wheel
- name: Repair Linux wheel
if: runner.os == 'Linux'
shell: bash
run: |
python3 -m pip install auditwheel
mkdir -p crates/goose-sdk/python/wheelhouse
auditwheel repair --plat manylinux_2_28_x86_64 -w crates/goose-sdk/python/wheelhouse crates/goose-sdk/python/dist/*.whl
rm crates/goose-sdk/python/dist/*.whl
mv crates/goose-sdk/python/wheelhouse/*.whl crates/goose-sdk/python/dist/
- name: Smoke test wheel
shell: bash
run: |
python -m venv .venv-wheel-test
if [ "${{ runner.os }}" = "Windows" ]; then
python_bin=".venv-wheel-test/Scripts/python.exe"
else
python_bin=".venv-wheel-test/bin/python"
fi
UV_NO_CONFIG=1 uv pip install --default-index https://pypi.org/simple --python "$python_bin" crates/goose-sdk/python/dist/*.whl
"$python_bin" -c 'import goose; print(goose.__name__)'
- name: Upload wheel artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: goose-sdk-wheel-${{ matrix.name }}
path: crates/goose-sdk/python/dist/*.whl
if-no-files-found: error
publish:
name: Publish wheels to PyPI
needs: build-wheels
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' && inputs.publish
environment: pypi
steps:
- name: Download wheel artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: goose-sdk-wheel-*
path: dist
merge-multiple: true
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
packages-dir: dist

View file

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check PR Author
run: |
# Get PR author

View file

@ -45,15 +45,15 @@ jobs:
fi
- name: Checkout the branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20
- name: Cache Node.js modules (documentation)
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
with:
path: ./documentation/node_modules
key: ${{ runner.os }}-documentation-${{ hashFiles('./documentation/package-lock.json') }}
@ -78,10 +78,10 @@ jobs:
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
- name: Upload GitHub Pages artifact
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: documentation/build
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0

View file

@ -30,14 +30,14 @@ jobs:
# We checkout the base branch (trusted) for building the scanner image,
# and only fetch recipe files from the PR for scanning.
- name: Checkout base branch (trusted code)
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.event.pull_request.base.sha }}
fetch-depth: 0
path: trusted
- name: Fetch PR recipe files only
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
@ -119,7 +119,7 @@ jobs:
- name: Set up Docker Buildx
if: steps.find_recipes.outputs.has_recipes == 'true' && steps.recipe_changes.outputs.recipe_files_changed == 'true'
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Prune Docker caches
if: steps.find_recipes.outputs.has_recipes == 'true' && steps.recipe_changes.outputs.recipe_files_changed == 'true'
@ -243,7 +243,7 @@ jobs:
- name: Upload scan artifacts
if: always() && steps.find_recipes.outputs.has_recipes == 'true' && steps.recipe_changes.outputs.recipe_files_changed == 'true'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: security-scan
path: ${{ runner.temp }}/security-scan/**

View file

@ -1,8 +1,10 @@
# This workflow is main release, needs to be manually tagged & pushed.
# This workflow builds release artifacts for release branches and publishes tagged releases.
on:
push:
paths-ignore:
- "documentation/**"
branches:
- "release/*"
tags:
- "v1.*"
@ -15,6 +17,11 @@ permissions:
pull-requests: write # Required for npm publish workflow
attestations: write # Required for SLSA build provenance attestations
env:
# Set this repository Actions variable to "true" in GitHub Settings > Secrets and variables
# > Actions > Variables after a release containing desktop app-update.yml has shipped.
ENABLE_MAC_NATIVE_AUTO_UPDATE: ${{ vars.ENABLE_MAC_NATIVE_AUTO_UPDATE || 'false' }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@ -34,8 +41,8 @@ jobs:
runs-on: ubuntu-latest
needs: [build-cli]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: download_cli.sh
path: download_cli.sh
@ -49,8 +56,8 @@ jobs:
id-token: write
contents: read
with:
signing: true
environment: signing
signing: ${{ startsWith(github.ref, 'refs/tags/') }}
environment: ${{ startsWith(github.ref, 'refs/tags/') && 'signing' || '' }}
secrets: inherit
# ------------------------------------------------------------
@ -62,8 +69,8 @@ jobs:
id-token: write
contents: read
with:
signing: true
environment: signing
signing: ${{ startsWith(github.ref, 'refs/tags/') }}
environment: ${{ startsWith(github.ref, 'refs/tags/') && 'signing' || '' }}
secrets: inherit
# ------------------------------------------------------------
@ -82,7 +89,7 @@ jobs:
contents: read
actions: read
with:
signing: true
signing: ${{ startsWith(github.ref, 'refs/tags/') }}
secrets: inherit
bundle-desktop-windows-cuda:
@ -92,7 +99,7 @@ jobs:
contents: read
actions: read
with:
signing: true
signing: ${{ startsWith(github.ref, 'refs/tags/') }}
windows_variant: cuda
secrets: inherit
@ -101,6 +108,7 @@ jobs:
# ------------------------------------
release:
name: Release
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [build-cli, install-script, bundle-desktop, bundle-desktop-intel, bundle-desktop-linux, bundle-desktop-windows, bundle-desktop-windows-cuda]
permissions:
@ -108,13 +116,25 @@ jobs:
id-token: write # Required for Sigstore OIDC signing
attestations: write # Required for SLSA build provenance attestations
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
merge-multiple: true
- name: Generate macOS update manifest
if: ${{ env.ENABLE_MAC_NATIVE_AUTO_UPDATE == 'true' }}
run: node ui/desktop/scripts/generate-mac-update-manifest.js --version "${GITHUB_REF_NAME}" --directory .
- name: Attest macOS update manifest
if: ${{ env.ENABLE_MAC_NATIVE_AUTO_UPDATE == 'true' }}
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: latest-mac.yml
- name: Attest build provenance
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: |
goose-*.tar.bz2
@ -163,3 +183,11 @@ jobs:
allowUpdates: true
omitBody: true
omitPrereleaseDuringUpdate: true
- name: Upload macOS update manifest
if: ${{ env.ENABLE_MAC_NATIVE_AUTO_UPDATE == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${GITHUB_REF_NAME}" latest-mac.yml --clobber
gh release upload stable latest-mac.yml --clobber

View file

@ -34,12 +34,12 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
@ -64,7 +64,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: SARIF file
path: results.sarif

View file

@ -29,7 +29,7 @@ jobs:
steps:
# Use the official stale action from GitHub
- name: 'Close Stale PRs'
uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
with:
# Authentication token with required permissions
repo-token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -30,7 +30,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0

View file

@ -16,7 +16,7 @@ jobs:
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Update Leaderboard
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0

View file

@ -223,7 +223,7 @@ jobs:
console.log(`Successfully updated discussion #${discussionNumber}`);
- name: 'Upload metrics artifact'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: health-metrics
path: health-metrics.json

View file

@ -17,12 +17,12 @@ jobs:
runs-on: ubuntu-latest
if: startsWith(github.event.pull_request.head.ref, 'release/') && github.event.pull_request.user.login == 'github-actions[bot]'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- name: Extract version from branch name
env:

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ tokenizer_files/
.DS_Store
.idea
.vscode
.zed/
*.log
tmp/

View file

@ -14,7 +14,7 @@ cargo build
```bash
cargo build # debug
cargo build --release # release
just release-binary # release + openapi
just release-binary # release binary
```
### Test
@ -33,28 +33,21 @@ cargo clippy --all-targets -- -D warnings
### UI
```bash
just generate-openapi # after server changes
just run-ui # start desktop
cd ui/desktop && pnpm run typecheck
cd ui/desktop && pnpm test # test UI
```
### Git
```bash
git commit -s # required for DCO sign-off
```
## Structure
```
crates/
├── goose # core logic
├── goose-acp-macros # ACP proc macros
├── goose-cli # CLI entry
├── goose-server # backend (binary: goosed)
├── goose-mcp # MCP extensions
├── goose-test # test utilities
└── goose-test-support # test helpers
evals/open-model-gym/ # benchmarking / evals
ui/desktop/ # Electron app
```
@ -70,7 +63,6 @@ ui/desktop/ # Electron app
# 1. cargo build
# 2. cargo test -p <crate>
# 3. cargo clippy --all-targets -- -D warnings
# 4. [if server] just generate-openapi
```
## Rules
@ -80,7 +72,7 @@ ui/desktop/ # Electron app
- Error: Use anyhow::Result
- Provider: Implement Provider trait see providers/base.rs
- MCP: Extensions in crates/goose-mcp/
- Server: Changes need just generate-openapi
- UI Desktop: Use ACP SDK types or local `src/types/*` types. Do not import generated OpenAPI types/client code from `ui/desktop/src/api`
## Code Quality
@ -112,7 +104,7 @@ remaining space for dynamic text.
## Never
- Never: Edit ui/desktop/openapi.json manually
- Never: Recreate `ui/desktop/src/api` or add `@hey-api/openapi-ts` to `ui/desktop`
- Cargo.toml: For human-authored dependency changes, use `cargo add` instead of manually editing dependency entries unless there is a specific reason not to.
- Cargo.toml: Automated dependency bump PRs are exempt; when manual edits are necessary, keep `Cargo.lock` consistent.
- Never: Skip cargo fmt
@ -121,6 +113,5 @@ remaining space for dynamic text.
## Entry Points
- CLI: crates/goose-cli/src/main.rs
- Server: crates/goose-server/src/main.rs
- UI: ui/desktop/src/main.ts
- Agent: crates/goose/src/agents/agent.rs

View file

@ -59,13 +59,7 @@ cd goose
Build Goose CLI:
```bash
cargo build --release -p goose-cli
```
Build Goose Server:
```bash
cargo build --release -p goose-server
cargo build --release -p goose-cli --bin goose
```
This command should give you a list of possible packages in the
@ -80,9 +74,9 @@ cargo test -p
cd ui/desktop
pnpm install
# Copy the server binary to the expected location
# Copy the goose binary to the expected location
mkdir -p src/bin
cp ../../target/release/goosed src/bin/
cp ../../target/release/goose src/bin/
```
### 4. Build the Application
@ -143,10 +137,10 @@ cd /path/to/goose/ui/desktop/out/goose-linux-x64
./goose 2>&1 | grep -v "GLib-GObject" | grep -v "browser_main_loop"
```
#### Server Binary Not Found
If you see "Could not find goosed binary", ensure you've:
1. Built the Rust backend: `cargo build --release -p goose-server`
2. Copied it to the right location: `cp ../../target/release/goosed src/bin/`
#### Goose Binary Not Found
If you see "Goose binary not found", ensure you've:
1. Built the Rust binary: `cargo build --release -p goose-cli --bin goose`
2. Copied it to the right location: `cp ../../target/release/goose src/bin/`
3. Rebuilt the application: `pnpm run make`
### Distribution-Specific Notes
@ -177,7 +171,7 @@ Building as Snap packages is not currently supported but may be added in the fut
For active development:
1. **Backend changes**: Rebuild with `cargo build --release -p goose-server` and copy the binary
1. **Backend changes**: Rebuild with `cargo build --release -p goose-cli --bin goose` and copy the binary
2. **Frontend changes**: Use `pnpm run start` for hot reload during development
3. **Full rebuild**: Run the complete build process above

View file

@ -29,16 +29,17 @@ It just means the change was too large for a first contribution. Start with some
### Issues
If you spot a bug or have a concrete proposal for a feature, please open an issue. This shows the community and
the maintainers the direction of your thinking.
If you spot a bug, please open an issue. This shows the community and the maintainers the direction of your
thinking.
For bugs, describe how to reproduce the problem as clearly as possible. If the issue involves an interaction
with an LLM, include a diagnostics report if possible.
### Discussions
If you have an idea but are not yet sure how it should work, open a discussion instead. Discussions are a good
place to explore design questions, alternatives, and whether something fits the goals of the project.
Before opening a feature request or beginning implementation, please start with a discussion in the
[goose-eng Discord channel](https://discord.com/channels/1287729918100246654/1514412780504088677). Discussions
are a good place to explore design questions, alternatives, and whether something fits the goals of the project.
If a change is large or touches multiple parts of the codebase, please start with a discussion before opening a PR.
This helps us align on direction before you spend time implementing something.
@ -173,41 +174,38 @@ The app opens a window and displays first-time setup. After completing setup, go
Make GUI changes in `ui/desktop`.
### Regenerating the OpenAPI schema
#### Troubleshooting: blank screen on `just run-ui`
The file `ui/desktop/openapi.json` is automatically generated during the build.
It is written by the `generate_schema` binary in `crates/goose-server`.
To update the spec without starting the UI, run:
If the app opens to a blank window (logs show `Cannot read properties of null (reading 'useRef')`), your `node_modules` is out of date and is loading two copies of React. Delete it and reinstall:
```
just generate-openapi
rm -rf ui/desktop/node_modules
cd ui && pnpm install
```
This command regenerates `ui/desktop/openapi.json` and then runs the UI's
`generate-api` script to rebuild the TypeScript client from that spec.
API changes should be made in the Rust source under `crates/goose-server/src/`.
See #8757.
### Debugging
To debug the Goose server, run it from an IDE. The configuration will depend on the IDE. The command to run is:
To debug the external ACP backend, run it from an IDE. The configuration will depend on the IDE. The command to run is:
```
export GOOSE_SERVER__SECRET_KEY=test
cargo run --package goose-server --bin goosed -- agent # or: `just run-server`
cargo run --package goose-cli --bin goose -- serve --platform desktop --host 127.0.0.1 --port 3000
```
The server listens on port `3000` by default; this can be changed by setting the
`GOOSE_PORT` environment variable.
The `debug-ui` recipe connects to `http://127.0.0.1:3000` by default. If the
backend uses another port, set `GOOSE_PORT` when starting the UI, or set
`GOOSE_EXTERNAL_BACKEND_URL` to the backend's HTTP base URL.
Once the server is running, start a UI and connect it to the server by running:
Once the backend is running, start a UI and connect it to the backend by running:
```
just debug-ui
```
The UI connects to the server started in the IDE, allowing breakpoints
and stepping through the server code while interacting with the UI.
The UI connects to the backend started in the IDE, allowing breakpoints
and stepping through the backend code while interacting with the UI.
## Creating a fork
@ -350,16 +348,6 @@ This project follows the [Conventional Commits](https://www.conventionalcommits.
[hermit]: https://cashapp.github.io/hermit/
[just]: https://github.com/casey/just?tab=readme-ov-file#installation
## Developer Certificate of Origin
This project requires a [Developer Certificate of Origin](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin) sign-offs on all commits. This is a statement indicating that you are allowed to make the contribution and that the project has the right to distribute it under its license. When you are ready to commit, use the `--signoff` or `-s` flag to attach the sign-off to your commit.
```
git commit --signoff ...
# OR
git commit -s ...
```
## Other Ways to Contribute
There are numerous ways to be an open source contributor and contribute to goose. We're here to help you on your way! Here are some suggestions to get started. If you have any questions or need help, feel free to reach out to us on [Discord](https://discord.gg/goose-oss).

View file

@ -26,8 +26,8 @@ goose's architecture is designed for extensibility. Organizations can create "re
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ goose-server (goosed)
REST API for all goose functionality
│ goose serve (ACP)
ACP HTTP/WebSocket server for custom clients
└─────────────────────────────────────────────────────────────────┘
@ -49,7 +49,7 @@ goose's architecture is designed for extensibility. Organizations can create "re
| 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 |
| Modify system prompts | `crates/goose/src/prompts/` | Low |
| Customize desktop branding | `ui/desktop/` (icons, names, colors) | Medium |
| Build a new UI (web, mobile) | Integrate with `goose-server` REST API | High |
| Build a new UI (web, mobile) | Integrate with `goose serve` over ACP | High |
| Create guided workflows | Recipes (YAML-based task definitions) | Low |
| Build complex multi-step workflows | Recipes with sub-recipes and subagents | Medium |
@ -304,40 +304,32 @@ export GOOSE_BUNDLE_NAME="InsightStream-goose"
**Goal**: Create an entirely new frontend while leveraging goose's backend.
goose provides two integration options for building custom UIs:
goose provides two ACP transport options for building custom clients:
### Option 1: REST API (goose-server)
### Option 1: ACP HTTP/WebSocket (`goose serve`)
Use goose-server for HTTP-based integrations (web apps, simple clients):
Use `goose serve` for process-separated integrations such as web apps, desktop shells, and other clients:
```bash
# Start the server
./target/release/goosed
GOOSE_SERVER__SECRET_KEY='a-long-random-secret' goose serve
# API available at http://localhost:3000
# ACP endpoint available at http://localhost:3284/acp
```
**Reference the OpenAPI spec** at `ui/desktop/openapi.json` for available endpoints:
- Session management
- Message streaming
- Extension control
- Configuration
HTTP clients authenticate with the `X-Secret-Key` header. Browser WebSocket clients use the same secret as a `?token=` query parameter because the browser WebSocket API cannot set custom headers:
**Key endpoints** for a minimal integration:
```
POST /sessions # Create a new session
POST /sessions/{id}/messages # Send a message (streaming response)
GET /sessions/{id} # Get session state
GET /extensions # List available extensions
POST /extensions/{name}/enable # Enable an extension
```text
ws://localhost:3284/acp?token=a-long-random-secret
```
**Handle streaming responses** - goose uses Server-Sent Events (SSE) for real-time responses.
For browser clients served from a non-loopback origin, pass the exact UI origin with `--allowed-origin`. When you pass `--allowed-origin`, it replaces the default loopback origin allowlist, so include every origin the client needs.
### Option 2: Agent Client Protocol (ACP)
For the ACP protocol and client flow, see [Agent Client Protocol clients](documentation/docs/guides/acp-clients.md).
For richer integrations (IDEs, desktop apps, embedded agents), use the **Agent Client Protocol (ACP)**—a standardized JSON-RPC protocol for AI agent communication over stdio or other transports.
### Option 2: Agent Client Protocol (ACP) over stdio
For richer local integrations (IDEs and embedded agents), run goose as an ACP agent over stdio.
ACP provides:
- **Bidirectional communication**: Agents can request permissions, stream updates, and receive cancellations
@ -410,11 +402,6 @@ For the full ACP specification, see the [Agent Client Protocol documentation](ht
### Technical Details
**REST API (goose-server)**:
- Server implementation: `crates/goose-server/src/routes/`
- OpenAPI generation: `just generate-openapi`
- API client example: `ui/desktop/src/api/` (generated TypeScript client)
**ACP**:
- ACP server implementation: `crates/goose/src/acp/server.rs`
- CLI integration: `crates/goose-cli/src/cli.rs` (Command::Acp)
@ -655,10 +642,10 @@ prompt: |
- find_patterns: Find similar features to model after
```
The AI can then invoke these sub-recipes using the `subagent` tool:
Recipes that define `sub_recipes` get the Summon extension automatically. The AI can invoke sub-recipes through Summon's `delegate` tool:
```
subagent(subrecipe: "find_files", parameters: {"search_term": "authentication"})
delegate(source: "find_files", parameters: {"search_term": "authentication"})
```
### Subagents: Dynamic Task Delegation
@ -678,28 +665,27 @@ prompt: |
To complete this task:
1. Spawn a subagent to analyze the frontend code:
subagent(instructions: "Analyze all React components in src/components/
and list their props and state management patterns")
delegate(instructions: "Analyze all React components in src/components/ and list their props and state management patterns")
2. Spawn another subagent for the backend:
subagent(instructions: "Document all API endpoints in src/api/
including their request/response schemas")
delegate(instructions: "Document all API endpoints in src/api/ including their request/response schemas")
3. Synthesize findings from both subagents into a unified report.
```
#### Parallel Subagent Execution
Multiple subagent calls in the same message execute in parallel:
Use `async: true` to run delegates in parallel, then collect each result with `load(source: "<task_id>")`:
```yaml
prompt: |
Run these analyses in parallel by making all subagent calls at once:
Run these analyses in parallel:
subagent(instructions: "Count lines of code by language")
subagent(instructions: "Find all TODO comments")
subagent(instructions: "List external dependencies")
delegate(instructions: "Count lines of code by language", async: true)
delegate(instructions: "Find all TODO comments", async: true)
delegate(instructions: "List external dependencies", async: true)
Use load(source: "<task_id>") for each returned task id.
Then combine the results into a codebase health report.
```
@ -711,22 +697,18 @@ Customize model, provider, or behavior per subagent:
prompt: |
Use a faster model for simple tasks:
subagent(
delegate(
instructions: "List all files modified in the last week",
settings: {
model: "gpt-4o-mini",
max_turns: 3
}
model: "gpt-4o-mini",
max_turns: 3
)
Use the full model for complex analysis:
subagent(
delegate(
instructions: "Review this code for security vulnerabilities",
settings: {
model: "claude-sonnet-4-20250514",
temperature: 0.1
}
model: "claude-sonnet-4-20250514",
temperature: 0.1
)
```
@ -738,7 +720,7 @@ Limit which extensions a subagent can access:
prompt: |
Create a sandboxed subagent with only file reading capabilities:
subagent(
delegate(
instructions: "Analyze the README files in this project",
extensions: ["developer"] # Only developer extension, no network access
)
@ -819,7 +801,7 @@ prompt: |
2. **Parallelize independent tasks** - Multiple subagent calls in one message run concurrently
3. **Use `sequential_when_repeated: true`** - For tasks that shouldn't run in parallel (e.g., database migrations)
4. **Scope extensions appropriately** - Give subagents only the tools they need
5. **Use summary mode (default)** - Subagents return concise summaries; use `summary: false` only when you need full conversation history
5. **Use background delegation for independent work** - Pass `async: true` to `delegate`, then collect results with `load(source: "<task_id>")`
6. **Handle failures gracefully** - Design workflows to continue even if one subagent fails
### Technical Details

3320
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,8 +8,8 @@ resolver = "2"
[workspace.package]
edition = "2021"
version = "1.36.0"
rust-version = "1.91.1"
version = "1.42.0"
rust-version = "1.94.1"
authors = ["AAIF <ai-oss-tools@block.xyz>"]
license = "Apache-2.0"
repository = "https://github.com/aaif-goose/goose"
@ -21,8 +21,9 @@ string_slice = "warn"
[workspace.dependencies]
rmcp = { version = "1.4", default-features = false, features = ["schemars", "auth"] }
agent-client-protocol-schema = { version = "0.12", default-features = false, features = ["unstable"] }
agent-client-protocol = { version = "0.11", default-features = false }
agent-client-protocol-schema = { version = "1.1", default-features = false, features = ["unstable"] }
agent-client-protocol = { version = "1.0", default-features = false }
agent-client-protocol-http = { version = "1.0", default-features = false, features = ["server", "unstable_cancel_request"] }
arboard = { version = "3", default-features = false }
anyhow = { version = "1.0.102", default-features = false, features = ["std"] }
async-stream = { version = "0.3.6", default-features = false }
@ -30,11 +31,11 @@ async-trait = { version = "0.1.89", default-features = false }
axum = { version = "0.8", default-features = false, features = ["http1", "http2", "json", "tokio", "query"] }
base64 = { version = "0.22.1", default-features = false, features = ["std"] }
bytes = { version = "1.10.1", default-features = false, features = ["std"] }
candle-core = { version = "0.10", default-features = false }
candle-nn = { version = "0.10", default-features = false }
candle-core = { version = "0.11", default-features = false }
candle-nn = { version = "0.11", default-features = false }
chrono = { version = "0.4.44", default-features = false, features = ["serde", "std"] }
clap = { version = "4.1.14", default-features = false, features = ["derive", "std", "help", "suggestions", "usage", "color", "error-context"] }
dirs = { version = "5", default-features = false }
dirs = { version = "6", default-features = false }
dotenvy = { version = "0.15.7", default-features = false }
env-lock = { version = "1", default-features = false }
etcetera = { version = "0.11", default-features = false }
@ -45,9 +46,9 @@ ignore = { version = "0.4.12", default-features = false }
include_dir = { version = "0.7", default-features = false }
indoc = { version = "2", default-features = false }
keyring = { version = "3.6.3", default-features = false, features = ["vendored"] }
lru = { version = "0.16", default-features = false }
lru = { version = "0.18", default-features = false }
once_cell = { version = "1.21.3", default-features = false, features = ["std"] }
rand = { version = "0.8.5", default-features = false, features = ["std"] }
rand = "0.10.1"
regex = { version = "1.12.3", default-features = false, features = ["std"] }
reqwest = { version = "0.13.2", default-features = false, features = ["multipart", "form"] }
rustls = { version = "0.23.31", default-features = false, features = ["aws_lc_rs", "std"] }
@ -56,27 +57,27 @@ serde = { version = "1.0.228", default-features = false, features = ["derive", "
serde_json = { version = "1.0.145", default-features = false, features = ["std"] }
serde_yaml = { version = "0.9.32", default-features = false }
shellexpand = { version = "3", default-features = false, features = ["base-0", "tilde"] }
strum = { version = "0.27.1", default-features = false, features = ["derive", "std"] }
strum = { version = "0.28.0", default-features = false, features = ["derive", "std"] }
tempfile = { version = "3.10.1", default-features = false }
thiserror = { version = "1.0.49", default-features = false }
thiserror = { version = "2.0.18", default-features = false }
tokio = { version = "1.48", default-features = false }
tokio-stream = { version = "0.1.16", default-features = false }
tokio-util = { version = "0.7.12", default-features = false }
tower-http = { version = "0.6.8", default-features = false }
tower-http = { version = "0.7.0", default-features = false }
tracing = { version = "0.1.43", default-features = false, features = ["std"] }
tracing-appender = { version = "0.2.1", default-features = false }
tracing-futures = { version = "0.2.4", default-features = false, features = ["futures-03", "std", "std-future"] }
tracing-subscriber = { version = "0.3.22", default-features = false, features = ["std"] }
urlencoding = { version = "2.1", default-features = false }
utoipa = { version = "4.2", default-features = false }
uuid = { version = "1.18", default-features = false, features = ["v4", "std"] }
uuid = { version = "1.23", default-features = false, features = ["v4", "std"] }
webbrowser = { version = "1", default-features = false }
which = { version = "8", default-features = false, features = ["real-sys"] }
winapi = { version = "0.3.9", default-features = false, features = ["wincred", "std"] }
wiremock = { version = "0.6", default-features = false }
zip = { version = "8", default-features = false, features = ["deflate"] }
serial_test = { version = "3", default-features = false }
sha2 = { version = "0.10.8", default-features = false, features = ["std"] }
sha2 = { version = "0.11", default-features = false }
shell-words = { version = "1", default-features = false, features = ["std"] }
test-case = { version = "3", default-features = false }
url = { version = "2.5.4", default-features = false, features = ["std"] }
@ -113,3 +114,12 @@ icu_locale = { version = "=2.1.1", default-features = false }
[patch.crates-io]
v8 = { path = "vendor/v8" }
cudaforge = { git = "https://github.com/jbg/cudaforge", rev = "e7c1967340e40673db98dc9e17da0f04834a456f" }
# Dependencies don't need debug info. You rarely step-debug into third-party
# crates, but their DWARF dominates `target/debug` — roughly 50-70% of each
# dependency rlib in this workspace is debug info. Dropping it for dependencies
# only shrinks the debug build substantially and speeds linking. The "*" spec
# matches all dependencies but NOT workspace members, so first-party crates keep
# full debug info and stay fully debuggable; release builds are unaffected.
[profile.dev.package."*"]
debug = false

15
I18N.md
View file

@ -100,11 +100,15 @@ Compiled files go to `src/i18n/compiled/` (gitignored).
The locale is resolved at startup in the following order:
1. **`GOOSE_LOCALE`** — explicit override (set on the `window` object or via env)
2. **`navigator.language`** — the browser/OS locale
3. **`"en"`** — fallback default
1. **Desktop language setting** — selected in Settings, unless set to System Default
2. **`GOOSE_LOCALE`** — explicit override from env/app config when the desktop setting is
System Default
3. **`navigator.language`** — the browser/OS locale
4. **`"en"`** — fallback default
The resolved locale is used for both text translations and all Intl formatting (dates, numbers, relative times).
The resolved locale is used for both text translations and all Intl formatting (dates, numbers,
relative times). Changing the desktop language setting reloads the renderer so startup-only locale
helpers pick up the new value.
## Date and number formatting
@ -134,7 +138,8 @@ This ensures date/number formatting uses the same locale as the rest of the UI.
1. Copy `src/i18n/messages/en.json` to a new file, e.g., `src/i18n/messages/ja.json`.
2. Translate the `defaultMessage` values. Keep ICU syntax intact (e.g., `{count, plural, ...}`).
3. Add the locale code to `SUPPORTED_LOCALES` in `src/i18n/index.ts`.
4. Optionally run `pnpm i18n:compile` to pre-compile.
4. Add the locale to the language selector in `src/components/settings/app/AppSettingsSection.tsx`.
5. Optionally run `pnpm i18n:compile` to pre-compile.
No other code changes are needed — `loadMessages()` dynamically imports the correct catalog at runtime.

View file

@ -13,18 +13,14 @@ check-everything:
cargo clippy --all-targets -- -D warnings
@echo " → Checking UI code formatting..."
cd ui/desktop && pnpm run lint:check
@echo " → Validating OpenAPI schema..."
./scripts/check-openapi-schema.sh
@echo ""
@echo "✅ All style checks passed!"
# Default release command
release-binary:
@echo "Building release version..."
cargo build --release
cargo build --release -p goose-cli --bin goose
@just copy-binary
@echo "Generating OpenAPI schema..."
cargo run -p goose-server --bin generate_schema
# Build Windows executable on a Windows host
[unix]
@ -34,7 +30,7 @@ release-windows:
[windows]
release-windows:
@powershell.exe -NoProfile -ExecutionPolicy Bypass -Command 'rustup target add x86_64-pc-windows-msvc; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; cargo build --release --target x86_64-pc-windows-msvc -p goose-server; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; Write-Host "Windows executable created at ./target/x86_64-pc-windows-msvc/release/goosed.exe"'
@powershell.exe -NoProfile -ExecutionPolicy Bypass -Command 'rustup target add x86_64-pc-windows-msvc; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; cargo build --release --target x86_64-pc-windows-msvc -p goose-cli --bin goose; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; Write-Host "Windows executable created at ./target/x86_64-pc-windows-msvc/release/goose.exe"'
# Build for Intel Mac
release-intel:
@ -43,15 +39,10 @@ release-intel:
@just copy-binary-intel
copy-binary BUILD_MODE="release":
@if [ -f ./target/{{BUILD_MODE}}/goosed ]; then \
echo "Copying goosed binary from target/{{BUILD_MODE}}..."; \
cp -p ./target/{{BUILD_MODE}}/goosed ./ui/desktop/src/bin/; \
else \
echo "Binary not found in target/{{BUILD_MODE}}"; \
exit 1; \
fi
@rm -f ./ui/desktop/src/bin/goosed
@if [ -f ./target/{{BUILD_MODE}}/goose ]; then \
echo "Copying goose CLI binary from target/{{BUILD_MODE}}..."; \
rm -f ./ui/desktop/src/bin/goose; \
cp -p ./target/{{BUILD_MODE}}/goose ./ui/desktop/src/bin/; \
else \
echo "goose CLI binary not found in target/{{BUILD_MODE}}"; \
@ -60,15 +51,10 @@ copy-binary BUILD_MODE="release":
# Copy binary command for Intel build
copy-binary-intel:
@if [ -f ./target/x86_64-apple-darwin/release/goosed ]; then \
echo "Copying Intel goosed binary to ui/desktop/src/bin with permissions preserved..."; \
cp -p ./target/x86_64-apple-darwin/release/goosed ./ui/desktop/src/bin/; \
else \
echo "Intel release binary not found."; \
exit 1; \
fi
@rm -f ./ui/desktop/src/bin/goosed
@if [ -f ./target/x86_64-apple-darwin/release/goose ]; then \
echo "Copying Intel goose CLI binary to ui/desktop/src/bin..."; \
rm -f ./ui/desktop/src/bin/goose; \
cp -p ./target/x86_64-apple-darwin/release/goose ./ui/desktop/src/bin/; \
else \
echo "Intel goose CLI binary not found."; \
@ -83,10 +69,11 @@ copy-binary-windows:
[windows]
copy-binary-windows:
@powershell.exe -NoProfile -ExecutionPolicy Bypass -Command 'if (Test-Path ./target/x86_64-pc-windows-msvc/release/goosed.exe) { \
@powershell.exe -NoProfile -ExecutionPolicy Bypass -Command 'if (Test-Path ./target/x86_64-pc-windows-msvc/release/goose.exe) { \
Write-Host "Copying Windows binary to ui/desktop/src/bin..."; \
New-Item -ItemType Directory -Force "./ui/desktop/src/bin" | Out-Null; \
Copy-Item -Path "./target/x86_64-pc-windows-msvc/release/goosed.exe" -Destination "./ui/desktop/src/bin/" -Force; \
Remove-Item -Path "./ui/desktop/src/bin/goosed.exe" -Force -ErrorAction SilentlyContinue; \
Copy-Item -Path "./target/x86_64-pc-windows-msvc/release/goose.exe" -Destination "./ui/desktop/src/bin/" -Force; \
} else { \
Write-Host "Windows binary not found." -ForegroundColor Red; \
exit 1; \
@ -112,7 +99,7 @@ run-ui-only:
cd ui/desktop && pnpm install && pnpm run start-gui
debug-ui:
@echo "🚀 Starting goose frontend in external backend mode"
@echo "🚀 Starting goose frontend in external ACP backend mode"
cd ui/desktop && \
export GOOSE_EXTERNAL_BACKEND=true && \
export GOOSE_SERVER__SECRET_KEY="${GOOSE_SERVER__SECRET_KEY:-test}" && \
@ -157,19 +144,8 @@ run-docs:
# Run server
run-server:
@echo "Running server..."
cargo run -p goose-server --bin goosed agent
# Check if OpenAPI schema is up-to-date
check-openapi-schema: generate-openapi
./scripts/check-openapi-schema.sh
# Generate OpenAPI specification without starting the UI
generate-openapi:
@echo "Generating OpenAPI schema..."
cargo run -p goose-server --bin generate_schema
@echo "Generating frontend API..."
cd ui/desktop && npx @hey-api/openapi-ts
@echo "Running external ACP backend..."
GOOSE_SERVER__SECRET_KEY="${GOOSE_SERVER__SECRET_KEY:-test}" cargo run -p goose-cli --bin goose -- serve --platform desktop --host 127.0.0.1 --port 3000
# Check if generated ACP schema and TypeScript types are up-to-date
check-acp-schema: generate-acp-types
@ -314,7 +290,6 @@ bump-version version:
@cd ui/desktop && npm pkg set "version={{ version }}"
# update Cargo.lock after bumping versions in Cargo.toml
@cargo update --workspace
@just set-openapi-version {{ version }}
# rebuild canonical model registry and mapping report from models.dev
build-canonical-models:
@ -329,14 +304,10 @@ prepare-release version:
Cargo.lock \
ui/desktop/package.json \
ui/pnpm-lock.yaml \
ui/desktop/openapi.json \
crates/goose/src/providers/canonical/data/canonical_models.json \
crates/goose/src/providers/canonical/data/provider_metadata.json
crates/goose-provider-types/src/canonical/data/canonical_models.json \
crates/goose-provider-types/src/canonical/data/provider_metadata.json
@git commit --message "chore(release): release version {{ version }}"
set-openapi-version version:
@jq '.info.version |= "{{ version }}"' ui/desktop/openapi.json > ui/desktop/openapi.json.tmp && mv ui/desktop/openapi.json.tmp ui/desktop/openapi.json
# extract version from Cargo.toml
get-tag-version:
@uvx --from=toml-cli toml get --toml-path=Cargo.toml "workspace.package.version"
@ -371,7 +342,6 @@ set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
### profile = --release or "" for debug
### allparam = OR/AND/ANY/NONE --workspace --all-features --all-targets
win-bld profile allparam:
cargo run {{profile}} -p goose-server --bin generate_schema
cargo build {{profile}} {{allparam}}
### Build just debug
@ -400,6 +370,7 @@ win-app-deps:
win-copy-win profile:
copy target{{s}}{{profile}}{{s}}*.exe ui{{s}}desktop{{s}}src{{s}}bin
copy target{{s}}{{profile}}{{s}}*.dll ui{{s}}desktop{{s}}src{{s}}bin
if exist ui{{s}}desktop{{s}}src{{s}}bin{{s}}goosed.exe del /f /q ui{{s}}desktop{{s}}src{{s}}bin{{s}}goosed.exe
### "Other" copy {release|debug} files to ui/desktop/src/bin
### s = os dependent file separator

View file

@ -1,5 +1,3 @@
> **🦆 goose has moved!** This project has moved from `block/goose` to the [Agentic AI Foundation (AAIF)](https://aaif.io/) at the Linux Foundation. Some links and references are still being updated — please bear with us during the transition.
<div align="center">
# goose
@ -16,8 +14,12 @@ _your native open source AI agent — desktop app, CLI, and API — for code, wo
<a href="https://insights.linuxfoundation.org/project/goose"><img src="https://insights.linuxfoundation.org/api/badge/health-score?project=goose"></a>
<a href="https://repology.org/project/goose-cli/versions"><img src="https://repology.org/badge/tiny-repos/goose-cli.svg" alt="Packaging status"></a>
</p>
<a href="https://trendshift.io/repositories/25298?utm_source=repository-badge&amp;utm_medium=badge&amp;utm_campaign=badge-repository-25298" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/25298" alt="aaif-goose%2Fgoose | Trendshift" width="250" height="55"/></a>
</div>
goose is a general-purpose AI agent that runs on your machine. Not just for code — use it for research, writing, automation, data analysis, or anything you need to get done.
A native desktop app for macOS, Linux, and Windows. A full CLI for terminal workflows. An API to embed it anywhere. Built in Rust for performance and portability.

View file

@ -1,134 +1,23 @@
# goose Release Manual Testing Checklist
## Version: {{VERSION}}
### Identify the high risk changes in this Release
Download the release builds from this PR. Once a build is ready, the actions bot will post a comment on this PR
with instructions on how to download and sign.
## Use the following script to create a risk assessment and testing plan:
```
./workflow_recipes/release_risk_check/run.sh {{VERSION}}
```
It will generate an analysis report in `/tmp/release_report_final.md` and perform testing is necessary for high risk pr changes.
It will generate an analysis report in `/tmp/release_report_final.md` and perform testing is necessary for high risk pr changes.
## Run the goose self-test recipe
## Regression Testing
goose run --recipe goose-self-test.yaml
Make a copy of this document for each version and check off as steps are verified.
## Have goose produce a test plan
### Provider Testing
Open the release candidate desktop app and have goose produce a test plan by pointing it at this PR. Use a prompt like
- [ ] Run `cd ui/desktop && pnpm run test:integration:providers` locally from the release branch and verify all providers/models work
- [ ] Launch goose, click reset providers, choose databricks and a model
> Look at the notes in PR <release PR> and the report at `/tmp/release_report_final.md` and investigate potential risks in this release. After familiarizing yourself with the scope of each change, produce a suggested test plan that I should follow before publishing the release.
### Starting Conversations
Test various ways to start a conversation:
- [ ] Open home and start a new conversation with "Hello"
- [ ] Agent responds
- [ ] Token count is updated after agent finishes
- [ ] Go to history and see there is a new entry
- [ ] Go back to the main screen, start a new conversation from the hub and see that it opens a new conversation
- [ ] Open history and click the Hello conversation - verify it loads
- [ ] Add a new message to this conversation and see that it is added
- [ ] Change the working directory of an existing conversation
- [ ] Ask "what is your working directory?"
- [ ] Response should match the new directory
- [ ] Open a new window, click chat in left side for new chat
- [ ] Type "create a tamagotchi game" in the chat input to test developer extension
### Recipes
#### Create Recipe from Session
- [ ] Start a simple chat conversation like "hi"
- [ ] Click "create a recipe from this session" in the bottom chat bar
- [ ] Recipe title, description and instructions should be filled in with details from the chat
- [ ] Add a few activities and params (params unused indicator should update if added to instructions/prompts or activities)
- [ ] Can launch create and run recipe - launches in a new window showing as a recipe agent chat with parameters filled in and interact with it
- [ ] Recipe should be saved in recipe library
#### Use Existing Recipe
- [ ] Pick trip planner from recipe hub (go/gooserecipes)
- [ ] See the warning whether to trust this recipe (only on fresh install)
- [ ] See the form pop up
- [ ] Fill in the form with "Africa" and "14 days"
- [ ] Check results are reasonable
- [ ] Ask how many days the trip is for - should say 14
#### Recipe Management
- [ ] Go to recipe manager and enter a new recipe to generate a joke
- [ ] See that it works if you run it
- [ ] Edit the recipe by bottom bar and click "View/Edit Recipe"
- [ ] Make it generate a limerick instead
- [ ] Check that the updated recipe works
- [ ] Delete the recipe from the recipe manager
- [ ] Verify recipe is actually deleted
#### Recipe from File
- [ ] Create a file `~/.config/goose/recipes/test-recipe.yaml` with the following content:
```yaml
recipe:
title: test recipe again
description: testing recipe again
instructions: The value of test_param is {{test_param}}
prompt: What is the value of test_param?
parameters:
- key: test_param
input_type: string
requirement: required
description: Enter value for test_param
```
- [ ] See that it shows up in the list of installed recipes
- [ ] Launch the recipe, see that it asks for test_param
- [ ] Enter a number, see that it pre-fills the prompt and tells you the value after you hit submit
- [ ] Go to hub and enter "what is the value of test_param"
- [ ] See a new chat that says it has no idea (recipe is no longer active)
### Extensions
#### Manual Extension Addition
- [ ] Can manually add an extension using random quotes from project
- [ ] Add new custom stdio extension with the following command and save:
- [ ] `node /ABSOLUTE/PATH/TO/goose/ui/desktop/tests/e2e/basic-mcp.ts` (use your actual project path)
- [ ] Should add and can chat to ask for a random quote
#### Playwright Extension
- [ ] Install the playwright extension from the extensions hub
- [ ] Tell it to open a browser and search on Google for cats
- [ ] Verify that the browser opens and navigates
#### Extension with Environment Variables
- [ ] Install an extension from deeplink that needs env variables:
- [ ] Use: `goose://extension?cmd=npx&arg=-y&arg=%40upstash%2Fcontext7-mcp&id=context7&name=Context7&description=Use%20up-to-date%20code%20and%20docs&env=TEST_ACCESS_TOKEN`
- [ ] Extension page should load with env variables modal showing
- [ ] Allow form input and saving extension
### Speech-to-Text (Local Model)
- [ ] Go to Settings > Chat > Voice dictation provider and select the small model
- [ ] Run a quick test that speech-to-text is working (click the mic button, speak, verify transcription)
- [ ] Also try OpenAI using your OpenAI key
### Settings
- [ ] Settings page loads and all tabs load
- [ ] Can change dark mode setting
### Follow-up Issues
Link any GitHub issues filed during testing:
---
**Tested by:** _____
**Date:** _____
**Notes:** _____
goose will produce a plan. Follow this plan to finish testing.

View file

@ -20,9 +20,10 @@ name = "generate_manpages"
path = "src/bin/generate_manpages.rs"
[dependencies]
clap_mangen = { version = "0.2", default-features = false }
clap_mangen = { version = "0.3", default-features = false }
goose = { path = "../goose", default-features = false }
goose-mcp = { path = "../goose-mcp", default-features = false }
goose-providers = { path = "../goose-providers", default-features = false }
rmcp = { workspace = true }
clap = { workspace = true }
cliclack = { version = "0.5", default-features = false }
@ -43,7 +44,7 @@ rustyline = { version = "18", default-features = false, features = ["custom-bind
tracing = { workspace = true }
chrono = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt", "json", "time"] }
shlex = { version = "1.3", default-features = false, features = ["std"] }
shlex = { version = "2.0", default-features = false, features = ["std"] }
async-trait = { workspace = true }
base64 = { workspace = true }
regex = { workspace = true }
@ -61,8 +62,9 @@ urlencoding = { workspace = true }
clap_complete = { version = "4", default-features = false }
comfy-table = { version = "7", default-features = false }
sha2 = { workspace = true }
sigstore-verify = { version = "0.8", default-features = false, optional = true }
sigstore-verify = { version = "0.10", default-features = false, optional = true }
axum.workspace = true
axum-server = { version = "0.8", default-features = false, optional = true }
clap_complete_nushell = { version = "4", default-features = false }
[target.'cfg(target_os = "windows")'.dependencies]
@ -73,6 +75,7 @@ winapi = { workspace = true }
default = [
"code-mode",
"local-inference",
"tui",
"aws-providers",
"telemetry",
"nostr",
@ -86,28 +89,37 @@ local-inference = ["goose/local-inference"]
aws-providers = ["goose/aws-providers"]
cuda = ["goose/cuda", "local-inference"]
vulkan = ["goose/vulkan", "local-inference"]
mlx = ["goose/mlx", "local-inference"]
telemetry = ["goose/telemetry"]
nostr = ["goose/nostr"]
otel = ["goose/otel"]
system-keyring = ["goose/system-keyring"]
tui = []
update = ["dep:sigstore-verify"]
portable-default = ["rustls-tls", "aws-providers", "telemetry", "otel"]
portable-default = ["rustls-tls", "aws-providers", "telemetry", "otel", "tui"]
# disables the update command
disable-update = []
rustls-tls = [
"dep:axum-server",
"reqwest/rustls",
"axum-server/tls-rustls",
"sigstore-verify?/rustls",
"goose/rustls-tls",
"goose-mcp/rustls-tls",
"goose-providers/rustls-tls",
]
native-tls = [
"dep:axum-server",
"reqwest/native-tls",
"axum-server/tls-openssl",
"sigstore-verify?/native-tls",
"goose/native-tls",
"goose-mcp/native-tls",
"goose-providers/native-tls",
]
[dev-dependencies]
env-lock.workspace = true
tempfile = { workspace = true }
test-case = { workspace = true }
tokio = { workspace = true }

View file

@ -29,6 +29,7 @@ use crate::commands::schedule::{
handle_schedule_sessions,
};
use crate::commands::session::{handle_session_list, handle_session_remove};
use crate::commands::skills::handle_skills_list;
use crate::recipes::extract_from_cli::extract_recipe_info_from_cli;
use crate::recipes::recipe::{explain_recipe, render_recipe_as_yaml};
use crate::session::{build_session, SessionBuilderConfig};
@ -42,14 +43,30 @@ use tracing::warn;
const GOOSE_SERVER_SECRET_KEY_ENV: &str = "GOOSE_SERVER__SECRET_KEY";
fn generate_serve_secret_key() -> String {
use rand::distributions::{Alphanumeric, DistString};
use rand::distr::{Alphanumeric, SampleString};
format!(
"goose-acp-{}",
Alphanumeric.sample_string(&mut rand::thread_rng(), 32)
Alphanumeric.sample_string(&mut rand::rng(), 32)
)
}
#[derive(clap::ValueEnum, Clone, Copy, Debug, Default, PartialEq, Eq)]
enum ServePlatform {
#[default]
Cli,
Desktop,
}
impl From<ServePlatform> for GoosePlatform {
fn from(platform: ServePlatform) -> Self {
match platform {
ServePlatform::Cli => GoosePlatform::GooseCli,
ServePlatform::Desktop => GoosePlatform::GooseDesktop,
}
}
}
#[derive(Parser)]
#[command(name = "goose", author, version, display_name = "", about, long_about = None)]
pub struct Cli {
@ -74,7 +91,7 @@ pub struct Identifier {
alias = "id",
value_name = "SESSION_ID",
help = "Session ID (e.g., '20250921_143022')",
long_help = "Specify a session ID directly. When used with --resume, will resume this specific session if it exists."
long_help = "Specify a session ID to resume. Requires --resume."
)]
pub session_id: Option<String>,
@ -359,6 +376,13 @@ pub struct RunBehavior {
)]
pub resume: bool,
/// Print generation statistics after completion
#[arg(
long = "stats",
help = "Print generation statistics after the run completes"
)]
pub stats: bool,
/// Scheduled job ID (used internally for scheduled executions)
#[arg(
long = "scheduled-job-id",
@ -384,7 +408,9 @@ async fn get_or_create_session_id(
let resolved_id = if resume {
let Some(id) = identifier else {
let sessions = session_manager.list_sessions().await?;
let sessions = session_manager
.list_sessions_by_types(&[SessionType::User])
.await?;
let session_id = sessions
.first()
.map(|s| s.id.clone())
@ -558,9 +584,13 @@ enum SessionCommand {
)]
relays: Vec<String>,
},
#[command(about = "Import a session from JSON or an encrypted Nostr share link")]
#[command(
about = "Import a session from JSON, a Claude Code / Codex / Pi .jsonl, or an encrypted Nostr share link"
)]
Import {
#[arg(help = "Path to a JSON session export, or a goose://sessions/nostr share link")]
#[arg(
help = "Path to a goose session export, a Claude Code, Codex, or Pi .jsonl transcript, or a goose://sessions/nostr share link"
)]
input: String,
#[arg(long = "nostr", help = "Treat input as an encrypted Nostr share link")]
@ -568,11 +598,9 @@ enum SessionCommand {
},
#[command(name = "diagnostics")]
Diagnostics {
/// Session identifier for generating diagnostics
#[command(flatten)]
identifier: Option<Identifier>,
/// Output path for the diagnostics zip file (optional, defaults to current directory)
#[arg(short = 'o', long)]
output: Option<PathBuf>,
},
@ -699,6 +727,13 @@ enum PluginCommand {
},
}
#[derive(Subcommand)]
enum SkillsCommand {
/// List all skills available to the goose agent
#[command(about = "List all skills available to the goose agent")]
List,
}
#[derive(Subcommand)]
enum RecipeCommand {
/// Validate a recipe file
@ -814,6 +849,18 @@ enum Command {
#[arg(long, default_value = "3284")]
port: u16,
#[arg(long, help = "Serve ACP over TLS")]
tls: bool,
#[arg(long = "tls-cert-path", value_name = "PATH")]
tls_cert_path: Option<String>,
#[arg(long = "tls-key-path", value_name = "PATH")]
tls_key_path: Option<String>,
#[arg(long, value_enum, default_value_t = ServePlatform::Cli)]
platform: ServePlatform,
#[arg(
long = "with-builtin",
value_name = "NAME",
@ -823,6 +870,20 @@ enum Command {
action = clap::ArgAction::Append
)]
builtins: Vec<String>,
#[arg(
long = "dangerously-unauthenticated",
help = "Start the ACP endpoint without requiring GOOSE_SERVER__SECRET_KEY"
)]
dangerously_unauthenticated: bool,
#[arg(
long = "allowed-origin",
value_name = "ORIGIN",
action = clap::ArgAction::Append,
help = "Allow an exact Origin value for ACP CORS; may be specified multiple times and replaces the default loopback origins"
)]
allowed_origins: Vec<String>,
},
/// Start or resume interactive chat sessions
@ -855,6 +916,15 @@ enum Command {
)]
fork: bool,
/// Open the session's conversation in $EDITOR before starting
#[arg(
long,
requires = "resume",
help = "Edit the session conversation in $EDITOR before starting",
long_help = "Open the session's conversation in your editor ($VISUAL / $EDITOR / vi) for modification before resuming. When combined with --fork, creates a new session from the edited result."
)]
edit: bool,
/// Show message history when resuming
#[arg(
long,
@ -910,6 +980,13 @@ enum Command {
command: RecipeCommand,
},
/// Skill utilities
#[command(about = "Skill utilities")]
Skills {
#[command(subcommand)]
command: SkillsCommand,
},
/// Manage plugins
#[command(about = "Manage plugins")]
Plugin {
@ -971,6 +1048,7 @@ enum Command {
},
/// Launch the goose terminal UI (TUI)
#[cfg(feature = "tui")]
#[command(
about = "Launch the goose terminal UI",
long_about = "Launch the goose terminal UI (the @aaif/goose npm package).\n\
@ -1042,8 +1120,9 @@ enum Command {
#[arg(long = "override-model", value_name = "MODEL")]
override_model: Option<String>,
/// Default `turn-limit` applied to checks that do not declare their
/// own.
/// Default `turn-limit` for orchestrated main-pass subprocesses and
/// for checks that do not declare their own. Does not cap the legacy
/// `--no-orchestrate` in-process main agent.
#[arg(long = "turn-limit", value_name = "N")]
turn_limit: Option<usize>,
@ -1107,7 +1186,6 @@ enum Command {
#[arg(long = "severity", value_name = "LEVEL", default_value = "medium")]
severity: String,
},
#[command(
name = "validate-extensions",
about = "Validate a bundled-extensions.json file",
@ -1122,8 +1200,8 @@ enum Command {
#[cfg(feature = "local-inference")]
#[derive(Subcommand)]
enum LocalModelsCommand {
/// Search HuggingFace for GGUF models
#[command(about = "Search HuggingFace for GGUF models")]
/// Search HuggingFace for local models
#[command(about = "Search HuggingFace for local GGUF and MLX models")]
Search {
/// Search query
query: String,
@ -1134,9 +1212,9 @@ enum LocalModelsCommand {
},
/// Download a model from HuggingFace
#[command(about = "Download a GGUF model (e.g. bartowski/Llama-3.2-1B-Instruct-GGUF:Q4_K_M)")]
#[command(about = "Download a local model from a search result")]
Download {
/// Model spec in user/repo:quantization format
/// Model spec/download id, e.g. user/repo:Q4_K_M or user/repo
spec: String,
},
@ -1273,8 +1351,10 @@ fn get_command_name(command: &Option<Command>) -> &'static str {
#[cfg(feature = "update")]
Some(Command::Update { .. }) => "update",
Some(Command::Recipe { .. }) => "recipe",
Some(Command::Skills { .. }) => "skills",
Some(Command::Plugin { .. }) => "plugin",
Some(Command::Term { .. }) => "term",
#[cfg(feature = "tui")]
Some(Command::Tui { .. }) => "tui",
#[cfg(feature = "local-inference")]
Some(Command::LocalModels { .. }) => "local-models",
@ -1297,13 +1377,39 @@ async fn handle_mcp_command(server: McpCommand) -> Result<()> {
Ok(())
}
async fn handle_serve_command(host: String, port: u16, builtins: Vec<String>) -> Result<()> {
struct ServeCommandArgs {
host: String,
port: u16,
tls: bool,
tls_cert_path: Option<String>,
tls_key_path: Option<String>,
platform: ServePlatform,
builtins: Vec<String>,
dangerously_unauthenticated: bool,
allowed_origins: Vec<String>,
}
async fn handle_serve_command(args: ServeCommandArgs) -> Result<()> {
use axum::http::HeaderValue;
use goose::acp::server_factory::{AcpServer, AcpServerFactoryConfig};
use goose::acp::transport::create_router;
use goose::config::paths::Paths;
use std::net::SocketAddr;
use std::sync::Arc;
use tracing::info;
use tracing::{info, warn};
let ServeCommandArgs {
host,
port,
tls,
tls_cert_path,
tls_key_path,
platform,
builtins,
dangerously_unauthenticated,
allowed_origins,
} = args;
let builtins = if builtins.is_empty() {
vec!["developer".to_string()]
@ -1327,25 +1433,93 @@ async fn handle_serve_command(host: String, port: u16, builtins: Vec<String>) ->
builtins,
data_dir: Paths::data_dir(),
config_dir: Paths::config_dir(),
goose_platform: GoosePlatform::GooseCli,
goose_platform: platform.into(),
additional_source_roots,
}));
let secret_key = std::env::var(GOOSE_SERVER_SECRET_KEY_ENV)
let env_secret = std::env::var(GOOSE_SERVER_SECRET_KEY_ENV)
.ok()
.map(|secret| secret.trim().to_string())
.filter(|secret| !secret.is_empty())
.unwrap_or_else(generate_serve_secret_key);
let router = create_router(server, secret_key);
.filter(|secret| !secret.is_empty());
let require_token = env_secret.is_some();
if !require_token && !dangerously_unauthenticated {
anyhow::bail!(
"{GOOSE_SERVER_SECRET_KEY_ENV} must be set to start `goose serve`; pass --dangerously-unauthenticated to run without ACP authentication"
);
}
if dangerously_unauthenticated && !require_token {
warn!(
"{GOOSE_SERVER_SECRET_KEY_ENV} is not set and --dangerously-unauthenticated was passed; the ACP endpoint will accept unauthenticated connections"
);
}
let additional_allowed_origins = allowed_origins
.into_iter()
.map(|origin| {
let origin = origin.trim();
if origin.is_empty() || origin == "*" {
anyhow::bail!("--allowed-origin must be a non-wildcard Origin value");
}
HeaderValue::from_str(origin).map_err(|error| {
anyhow::anyhow!("invalid --allowed-origin value `{origin}`: {error}")
})
})
.collect::<Result<Vec<_>>>()?;
let secret_key = env_secret.unwrap_or_else(generate_serve_secret_key);
let router = create_router(
server,
secret_key,
require_token,
additional_allowed_origins,
);
let config = Config::global();
let tls_cert_path =
tls_cert_path.or_else(|| config.get_param::<String>("GOOSE_TLS_CERT_PATH").ok());
let tls_key_path =
tls_key_path.or_else(|| config.get_param::<String>("GOOSE_TLS_KEY_PATH").ok());
let tls = tls
|| config.get_param::<bool>("GOOSE_TLS").unwrap_or(false)
|| tls_cert_path.is_some()
|| tls_key_path.is_some();
let addr: SocketAddr = format!("{}:{}", host, port).parse()?;
info!("Starting ACP server on {}", addr);
if tls {
#[cfg(any(feature = "rustls-tls", feature = "native-tls"))]
{
let tls_setup = goose::acp::transport::tls::setup_tls(
tls_cert_path.as_deref(),
tls_key_path.as_deref(),
)
.await?;
info!("Starting ACP server on https://{}", addr);
let listener = tokio::net::TcpListener::bind(addr).await?;
axum::serve(
listener,
router.into_make_service_with_connect_info::<SocketAddr>(),
)
.await?;
#[cfg(feature = "rustls-tls")]
axum_server::bind_rustls(addr, tls_setup.config)
.serve(router.into_make_service_with_connect_info::<SocketAddr>())
.await?;
#[cfg(feature = "native-tls")]
axum_server::bind_openssl(addr, tls_setup.config)
.serve(router.into_make_service_with_connect_info::<SocketAddr>())
.await?;
}
#[cfg(not(any(feature = "rustls-tls", feature = "native-tls")))]
{
let _ = (tls_cert_path, tls_key_path);
anyhow::bail!(
"TLS was requested but no TLS backend is enabled. \
Enable the `rustls-tls` or `native-tls` feature."
);
}
} else {
info!("Starting ACP server on http://{}", addr);
let listener = tokio::net::TcpListener::bind(addr).await?;
axum::serve(
listener,
router.into_make_service_with_connect_info::<SocketAddr>(),
)
.await?;
}
Ok(())
}
@ -1430,6 +1604,7 @@ async fn handle_interactive_session(
identifier: Option<Identifier>,
resume: bool,
fork: bool,
edit: bool,
history: bool,
session_opts: SessionOptions,
extension_opts: ExtensionOptions,
@ -1469,12 +1644,31 @@ async fn handle_interactive_session(
let goose_mode = Config::global().get_goose_mode().unwrap_or_default();
let mut session_id = get_or_create_session_id(identifier, resume, false, goose_mode).await?;
if fork {
if let Some(id) = session_id {
if edit || fork {
if let Some(ref id) = session_id {
let session_manager = SessionManager::instance();
let original = session_manager.get_session(&id, false).await?;
let copied = session_manager.copy_session(&id, original.name).await?;
session_id = Some(copied.id);
let original = session_manager.get_session(id, true).await?;
let target_id = if fork {
let copied = session_manager
.copy_session(id, original.name.clone())
.await?;
let copied_id = copied.id.clone();
session_id = Some(copied.id);
copied_id
} else {
id.clone()
};
if edit {
let conversation = original
.conversation
.ok_or_else(|| anyhow::anyhow!("session has no messages to edit"))?;
let edited = crate::session::editor::edit_conversation(&conversation)?;
session_manager
.replace_conversation(&target_id, &edited)
.await?;
}
}
}
@ -1499,6 +1693,7 @@ async fn handle_interactive_session(
quiet: false,
output_format: "text".to_string(),
container: session_opts.container.map(Container::new),
stats: false,
})
.await;
@ -1523,7 +1718,7 @@ async fn log_session_completion(
let (total_tokens, message_count) = session
.get_session()
.await
.map(|m| (m.total_tokens.unwrap_or(0), m.message_count))
.map(|m| (m.usage.total_tokens.unwrap_or(0), m.message_count))
.unwrap_or((0, 0));
tracing::info!(
@ -1711,6 +1906,7 @@ async fn handle_run_command(
quiet: output_opts.quiet,
output_format: output_opts.output_format,
container: session_opts.container.map(Container::new),
stats: run_behavior.stats,
})
.await;
@ -1799,6 +1995,12 @@ fn handle_recipe_subcommand(command: RecipeCommand) -> Result<()> {
}
}
async fn handle_skills_subcommand(command: SkillsCommand) -> Result<()> {
match command {
SkillsCommand::List => handle_skills_list().await,
}
}
async fn handle_term_subcommand(command: TermCommand) -> Result<()> {
match command {
TermCommand::Init {
@ -1812,20 +2014,40 @@ async fn handle_term_subcommand(command: TermCommand) -> Result<()> {
}
}
#[cfg(feature = "local-inference")]
fn print_download_progress(manager: &goose::download_manager::DownloadManager) {
let Some(progress) = manager
.list_progress()
.into_iter()
.find(|progress| progress.status == goose::download_manager::DownloadStatus::Downloading)
else {
return;
};
print!(
"\r {:.1}% ({:.0}MB / {:.0}MB)",
progress.progress_percent,
progress.bytes_downloaded as f64 / (1024.0 * 1024.0),
progress.total_bytes as f64 / (1024.0 * 1024.0),
);
use std::io::Write;
std::io::stdout().flush().ok();
}
#[cfg(feature = "local-inference")]
async fn handle_local_models_command(command: LocalModelsCommand) -> Result<()> {
use goose::providers::local_inference::hf_models;
use goose::providers::local_inference::local_model_registry::{
get_registry, model_id_from_repo, LocalModelEntry,
};
use goose::providers::local_inference::local_model_registry::get_registry;
goose::providers::local_inference::configure_huggingface_auth();
match command {
LocalModelsCommand::Search { query, limit } => {
println!("Searching HuggingFace for '{}'...", query);
let results = hf_models::search_gguf_models(&query, limit).await?;
let results = hf_models::search_local_models(&query, limit).await?;
if results.is_empty() {
println!("No GGUF models found.");
println!("No compatible local models found.");
return Ok(());
}
@ -1834,107 +2056,68 @@ async fn handle_local_models_command(command: LocalModelsCommand) -> Result<()>
"\n{} (by {}) — {} downloads",
model.model_name, model.author, model.downloads
);
for file in &model.gguf_files {
let size = if file.size_bytes > 0 {
for variant in &model.variants {
let size = if variant.size_bytes > 0 {
format!(
"{:.1}GB",
file.size_bytes as f64 / (1024.0 * 1024.0 * 1024.0)
variant.size_bytes as f64 / (1024.0 * 1024.0 * 1024.0)
)
} else {
"unknown".to_string()
};
println!(" {}{}", file.quantization, size);
let support = if variant.supported {
String::new()
} else {
format!(
" ({})",
variant
.unsupported_reason
.as_deref()
.unwrap_or("unsupported on this platform")
)
};
println!(
" [{}] {} — {} — {}{}",
variant.format, variant.label, size, variant.description, support
);
if variant.supported {
println!(
" Download: goose local-models download '{}'",
variant.download_id
);
}
}
println!(
" Download: goose local-models download {}:<quantization>",
model.repo_id
);
}
}
LocalModelsCommand::Download { spec } => {
println!("Resolving {}...", spec);
let (repo_id, file) = hf_models::resolve_model_spec(&spec).await?;
let model_id = model_id_from_repo(&repo_id, &file.quantization);
let local_path =
goose::config::paths::Paths::in_data_dir("models").join(&file.filename);
let manager = goose::download_manager::get_download_manager();
let resolve_task = hf_models::resolve_local_model_spec(&spec);
tokio::pin!(resolve_task);
let resolved = loop {
tokio::select! {
result = &mut resolve_task => break result?,
_ = tokio::time::sleep(std::time::Duration::from_millis(500)) => {
print_download_progress(manager);
}
}
};
let model_id = resolved.model_id();
let total_size = resolved.total_size();
println!(
"Downloading {} ({})...",
"\nDownloaded {} ({}). Registering...",
model_id,
if file.size_bytes > 0 {
format!(
"{:.1}GB",
file.size_bytes as f64 / (1024.0 * 1024.0 * 1024.0)
)
if total_size > 0 {
format!("{:.1}GB", total_size as f64 / (1024.0 * 1024.0 * 1024.0))
} else {
"unknown size".to_string()
}
);
// Register
let entry = LocalModelEntry {
id: model_id.clone(),
repo_id: repo_id.clone(),
filename: file.filename.clone(),
quantization: file.quantization.clone(),
local_path: local_path.clone(),
source_url: file.download_url.clone(),
settings: Default::default(),
size_bytes: file.size_bytes,
mmproj_path: None,
mmproj_source_url: None,
mmproj_size_bytes: 0,
shard_files: vec![],
};
let model_id = hf_models::register_resolved_model(resolved, &spec)?;
{
let mut registry = get_registry()
.lock()
.map_err(|_| anyhow::anyhow!("Failed to acquire registry lock"))?;
registry.add_model(entry)?;
}
// Download
let manager = goose::download_manager::get_download_manager();
manager
.download_model(
format!("{}-model", model_id),
file.download_url,
local_path,
None,
)
.await?;
// Poll progress
loop {
if let Some(progress) = manager.get_progress(&format!("{}-model", model_id)) {
match progress.status {
goose::download_manager::DownloadStatus::Downloading => {
print!(
"\r {:.1}% ({:.0}MB / {:.0}MB)",
progress.progress_percent,
progress.bytes_downloaded as f64 / (1024.0 * 1024.0),
progress.total_bytes as f64 / (1024.0 * 1024.0),
);
use std::io::Write;
std::io::stdout().flush().ok();
}
goose::download_manager::DownloadStatus::Completed => {
println!("\nDownloaded: {}", model_id);
break;
}
goose::download_manager::DownloadStatus::Failed => {
let err = progress.error.unwrap_or_default();
anyhow::bail!("Download failed: {}", err);
}
goose::download_manager::DownloadStatus::Cancelled => {
println!("\nDownload cancelled.");
break;
}
}
}
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}
println!("Registered: {}", model_id);
}
LocalModelsCommand::List => {
let registry = get_registry()
@ -1947,12 +2130,16 @@ async fn handle_local_models_command(command: LocalModelsCommand) -> Result<()>
return Ok(());
}
println!("{:<50} {:<10} Downloaded", "ID", "Quant");
println!("{}", "-".repeat(70));
println!(
"{:<50} {:<10} {:<12} Downloaded",
"ID", "Backend", "Variant"
);
println!("{}", "-".repeat(88));
for m in models {
println!(
"{:<50} {:<10} {}",
"{:<50} {:<10} {:<12} {}",
m.id,
m.backend_id.as_deref().unwrap_or("llamacpp"),
m.quantization,
if m.is_downloaded() { "" } else { "" }
);
@ -1963,11 +2150,8 @@ async fn handle_local_models_command(command: LocalModelsCommand) -> Result<()>
.lock()
.map_err(|_| anyhow::anyhow!("Failed to acquire registry lock"))?;
if let Some(entry) = registry.get_model(&id) {
if entry.local_path.exists() {
std::fs::remove_file(&entry.local_path)?;
}
registry.remove_model(&id)?;
if registry.get_model(&id).is_some() {
registry.delete_model(&id)?;
println!("Deleted model: {}", id);
} else {
println!("Model not found: {}", id);
@ -2012,6 +2196,7 @@ async fn handle_default_session() -> Result<()> {
quiet: false,
output_format: "text".to_string(),
container: None,
stats: false,
})
.await;
session.interactive(None).await
@ -2047,8 +2232,27 @@ pub async fn cli() -> anyhow::Result<()> {
Some(Command::Serve {
host,
port,
tls,
tls_cert_path,
tls_key_path,
platform,
builtins,
}) => handle_serve_command(host, port, builtins).await,
dangerously_unauthenticated,
allowed_origins,
}) => {
handle_serve_command(ServeCommandArgs {
host,
port,
tls,
tls_cert_path,
tls_key_path,
platform,
builtins,
dangerously_unauthenticated,
allowed_origins,
})
.await
}
Some(Command::Session {
command: Some(cmd), ..
}) => handle_session_subcommand(cmd).await,
@ -2057,6 +2261,7 @@ pub async fn cli() -> anyhow::Result<()> {
identifier,
resume,
fork,
edit,
history,
session_opts,
extension_opts,
@ -2065,6 +2270,7 @@ pub async fn cli() -> anyhow::Result<()> {
identifier,
resume,
fork,
edit,
history,
session_opts,
extension_opts,
@ -2110,8 +2316,10 @@ pub async fn cli() -> anyhow::Result<()> {
Ok(())
}
Some(Command::Recipe { command }) => handle_recipe_subcommand(command),
Some(Command::Skills { command }) => handle_skills_subcommand(command).await,
Some(Command::Plugin { command }) => handle_plugin_subcommand(command),
Some(Command::Term { command }) => handle_term_subcommand(command).await,
#[cfg(feature = "tui")]
Some(Command::Tui { args }) => crate::commands::tui::handle_tui(args),
#[cfg(feature = "local-inference")]
Some(Command::LocalModels { command }) => handle_local_models_command(command).await,
@ -2228,4 +2436,134 @@ mod tests {
let help = String::from_utf8(buffer).expect("utf8");
assert!(help.contains("nu"));
}
#[test]
fn skills_command_accepts_list_subcommand() {
let cli = Cli::try_parse_from(["goose", "skills", "list"]).expect("parse failed");
match cli.command {
Some(Command::Skills {
command: SkillsCommand::List,
}) => {}
_ => panic!("expected skills list command"),
}
}
#[test]
fn serve_command_accepts_dangerously_unauthenticated_flag() {
let cli = Cli::try_parse_from([
"goose",
"serve",
"--dangerously-unauthenticated",
"--allowed-origin",
"app://localhost",
"--allowed-origin",
"https://app.example",
])
.expect("parse failed");
match cli.command {
Some(Command::Serve {
dangerously_unauthenticated,
allowed_origins,
..
}) => {
assert!(dangerously_unauthenticated);
assert_eq!(
allowed_origins,
vec!["app://localhost", "https://app.example"]
);
}
_ => panic!("expected serve command"),
}
}
#[test]
fn review_command_accepts_options() {
let cli = Cli::try_parse_from([
"goose",
"review",
"origin/main...HEAD",
"--prompt",
"REVIEW.md",
"--model",
"test-model",
"--provider",
"openai",
"--override-model",
"check-model",
"--turn-limit",
"4",
"--dry-run",
"--quiet",
"--no-orchestrate",
"--instructions",
"focus on correctness",
"--files",
"src/lib.rs",
"--check-filter",
"security",
"--check-scope",
".agents",
"--checks-only",
"--summary-only",
"--severity",
"low",
])
.expect("parse failed");
match cli.command {
Some(Command::Review {
range,
prompt,
model,
provider,
override_model,
turn_limit,
dry_run,
quiet,
no_orchestrate,
instructions,
files,
check_filter,
check_scope,
checks_only,
summary_only,
severity,
}) => {
assert_eq!(range.as_deref(), Some("origin/main...HEAD"));
assert_eq!(prompt.as_deref(), Some(std::path::Path::new("REVIEW.md")));
assert_eq!(model.as_deref(), Some("test-model"));
assert_eq!(provider.as_deref(), Some("openai"));
assert_eq!(override_model.as_deref(), Some("check-model"));
assert_eq!(turn_limit, Some(4));
assert!(dry_run);
assert!(quiet);
assert!(no_orchestrate);
assert_eq!(instructions.as_deref(), Some("focus on correctness"));
assert_eq!(files, vec!["src/lib.rs"]);
assert_eq!(check_filter, vec!["security"]);
assert_eq!(
check_scope.as_deref(),
Some(std::path::Path::new(".agents"))
);
assert!(checks_only);
assert!(summary_only);
assert_eq!(severity, "low");
}
_ => panic!("expected review command"),
}
}
#[cfg(feature = "tui")]
#[test]
fn tui_command_accepts_trailing_args() {
let cli =
Cli::try_parse_from(["goose", "tui", "--", "--theme", "dark"]).expect("parse failed");
match cli.command {
Some(Command::Tui { args }) => assert_eq!(args, vec!["--theme", "dark"]),
_ => panic!("expected tui command"),
}
}
}

View file

@ -19,20 +19,31 @@ use goose::config::{
configure_tetrate, Config, ConfigError, ExperimentManager, ExtensionEntry, GooseMode,
PermissionManager,
};
use goose::model::ModelConfig;
#[cfg(feature = "telemetry")]
use goose::posthog::{get_telemetry_choice, TELEMETRY_ENABLED_KEY};
use goose::providers::base::ConfigKey;
use goose::providers::provider_test::test_provider_configuration;
use goose::providers::{create, providers, retry_operation, RetryConfig};
use goose::session::SessionType;
use goose_providers::thinking::ThinkingEffort;
use serde_json::Value;
use std::collections::HashMap;
use std::io::IsTerminal;
use std::io::{IsTerminal, Write};
// useful for light themes where there is no discernible colour contrast between
// cursor-selected and cursor-unselected items.
const MULTISELECT_VISIBILITY_HINT: &str = "<";
const SHOW_CURSOR: &[u8] = b"\x1b[?25h";
struct CursorRestoreGuard;
impl Drop for CursorRestoreGuard {
fn drop(&mut self) {
let mut stdout = std::io::stdout().lock();
let _ = stdout.write_all(SHOW_CURSOR);
let _ = stdout.flush();
}
}
pub async fn handle_configure() -> anyhow::Result<()> {
if !std::io::stdin().is_terminal() {
@ -42,6 +53,7 @@ pub async fn handle_configure() -> anyhow::Result<()> {
);
}
let _cursor_restore = CursorRestoreGuard;
let config = Config::global();
if !config.exists() {
@ -339,8 +351,7 @@ async fn handle_oauth_configuration(provider_name: &str, key_name: &str) -> anyh
));
// Create a temporary provider instance to handle OAuth
let temp_model = ModelConfig::new("temp")?.with_canonical_limits(provider_name);
match create(provider_name, temp_model, Vec::new()).await {
match create(provider_name, Vec::new()).await {
Ok(provider) => match provider.configure_oauth().await {
Ok(_) => {
let _ = cliclack::log::success("OAuth authentication completed successfully!");
@ -365,7 +376,12 @@ async fn handle_oauth_configuration(provider_name: &str, key_name: &str) -> anyh
}
}
fn interactive_model_search(models: &[String]) -> anyhow::Result<String> {
const UNLISTED_MODEL_KEY: &str = "__unlisted__";
fn interactive_model_search(
models: &[String],
provider_meta: &goose::providers::base::ProviderMetadata,
) -> anyhow::Result<String> {
const MAX_VISIBLE: usize = 30;
let mut query = String::new();
@ -395,7 +411,20 @@ fn interactive_model_search(models: &[String]) -> anyhow::Result<String> {
};
if filtered.is_empty() {
let _ = cliclack::log::warning("No matching models. Try a different search.");
let selection = cliclack::select("No matching models. What would you like to do?")
.item(
"__new_search__",
"Start a new search...",
"Enter a different search term",
)
.item(UNLISTED_MODEL_KEY, "Enter a model not listed...", "")
.interact()?;
if selection == UNLISTED_MODEL_KEY {
return prompt_unlisted_model(provider_meta);
}
query.clear();
continue;
}
@ -429,6 +458,12 @@ fn interactive_model_search(models: &[String]) -> anyhow::Result<String> {
);
}
items.push((
UNLISTED_MODEL_KEY.to_string(),
"Enter a model not listed...".to_string(),
"",
));
let selection = cliclack::select("Select a model:")
.items(&items)
.interact()?;
@ -438,6 +473,8 @@ fn interactive_model_search(models: &[String]) -> anyhow::Result<String> {
} else if selection == "__new_search__" {
query.clear();
continue;
} else if selection == UNLISTED_MODEL_KEY {
return prompt_unlisted_model(provider_meta);
} else {
return Ok(selection);
}
@ -449,7 +486,6 @@ fn select_model_from_list(
provider_meta: &goose::providers::base::ProviderMetadata,
) -> anyhow::Result<String> {
const MAX_MODELS: usize = 10;
const UNLISTED_MODEL_KEY: &str = "__unlisted__";
// Smart model selection:
// If we have more than MAX_MODELS models, show the recommended models with additional search option.
@ -488,14 +524,14 @@ fn select_model_from_list(
.interact()?;
if selection == "search_all" {
Ok(interactive_model_search(models)?)
interactive_model_search(models, provider_meta)
} else if selection == UNLISTED_MODEL_KEY {
prompt_unlisted_model(provider_meta)
} else {
Ok(selection)
}
} else {
Ok(interactive_model_search(models)?)
interactive_model_search(models, provider_meta)
}
} else {
let mut model_items: Vec<(String, String, &str)> =
@ -736,11 +772,11 @@ pub async fn configure_provider_dialog() -> anyhow::Result<bool> {
let spin = spinner();
spin.start("Attempting to fetch supported models...");
let temp_model_config =
ModelConfig::new(&provider_meta.default_model)?.with_canonical_limits(provider_name);
let temp_provider = create(provider_name, temp_model_config, Vec::new()).await?;
let temp_provider = create(provider_name, Vec::new()).await?;
let models_res = retry_operation(&RetryConfig::default(), || async {
temp_provider.fetch_recommended_models().await
temp_provider
.fetch_recommended_models(goose::model_config::global_toolshim())
.await
})
.await;
spin.stop(style("Model fetch complete").green());
@ -765,20 +801,20 @@ pub async fn configure_provider_dialog() -> anyhow::Result<bool> {
{
let supports_thinking = match temp_provider.fetch_model_info(&model).await {
Ok(model_info) => model_info.reasoning,
Err(_) => goose::model::ModelConfig::new(&model)
.map(|c| c.is_reasoning_model())
.unwrap_or(false),
Err(_) => goose_providers::model::ModelConfig::new(&model).is_reasoning_model(),
};
if supports_thinking {
let effort: &str = cliclack::select("Select thinking effort:")
let effort: ThinkingEffort = cliclack::select("Select thinking effort:")
.item("off", "Off - No extended thinking", "")
.item("low", "Low - Better latency, lighter reasoning", "")
.item("medium", "Medium - Moderate thinking", "")
.item("high", "High - Deep reasoning", "")
.item("max", "Max - No constraints on thinking depth", "")
.initial_value("off")
.interact()?;
.interact()?
.parse()
.map_err(|_| anyhow::anyhow!("invalid thinking effort"))?;
config.set_goose_thinking_effort(effort)?;
}
}
@ -1080,6 +1116,7 @@ fn configure_stdio_extension() -> anyhow::Result<()> {
env_keys,
description,
timeout: Some(timeout),
cwd: None,
bundled: None,
available_tools: Vec::new(),
},
@ -1551,7 +1588,7 @@ pub async fn configure_tool_permissions_dialog() -> anyhow::Result<()> {
let model: String = config
.get_goose_model()
.expect("No model configured. Please set model first");
let model_config = ModelConfig::new(&model)?.with_canonical_limits(&provider_name);
let model_config = goose::model_config::model_config_from_user_config(&provider_name, &model)?;
let agent = Agent::new();
@ -1588,8 +1625,10 @@ pub async fn configure_tool_permissions_dialog() -> anyhow::Result<()> {
}
let extensions = extension_config.into_iter().collect::<Vec<_>>();
let new_provider = create(&provider_name, model_config, extensions).await?;
agent.update_provider(new_provider, &session.id).await?;
let new_provider = create(&provider_name, extensions).await?;
agent
.update_provider(new_provider, model_config, &session.id)
.await?;
let permission_manager = PermissionManager::instance();
let selected_tools = agent
@ -1771,22 +1810,21 @@ pub async fn handle_openrouter_auth() -> anyhow::Result<()> {
// Test configuration - get the model that was configured
println!("\nTesting configuration...");
let configured_model: String = config.get_goose_model()?;
let model_config = match goose::model::ModelConfig::new(&configured_model) {
Ok(config) => config.with_canonical_limits("openrouter"),
Err(e) => {
eprintln!("⚠️ Invalid model configuration: {}", e);
eprintln!("Your settings have been saved. Please check your model configuration.");
return Ok(());
}
};
let model_config =
match goose::model_config::model_config_from_user_config("openrouter", &configured_model) {
Ok(config) => config,
Err(e) => {
eprintln!("⚠️ Invalid model configuration: {}", e);
eprintln!("Your settings have been saved. Please check your model configuration.");
return Ok(());
}
};
match create("openrouter", model_config, Vec::new()).await {
match create("openrouter", Vec::new()).await {
Ok(provider) => {
let provider_model_config = provider.get_model_config();
let test_result = provider
.complete(
&provider_model_config,
"",
&model_config,
"You are goose, an AI assistant.",
&[Message::user().with_text("Say 'Configuration test successful!'")],
&[],
@ -1852,16 +1890,14 @@ pub async fn handle_tetrate_auth() -> anyhow::Result<()> {
// Test configuration
println!("\nTesting configuration...");
let configured_model: String = config.get_goose_model()?;
let model_config = match goose::model::ModelConfig::new(&configured_model) {
Ok(config) => config.with_canonical_limits("tetrate"),
Err(e) => {
eprintln!("⚠️ Invalid model configuration: {}", e);
eprintln!("Your settings have been saved. Please check your model configuration.");
return Ok(());
}
};
if let Err(e) = goose::model_config::model_config_from_user_config("tetrate", &configured_model)
{
eprintln!("⚠️ Invalid model configuration: {}", e);
eprintln!("Your settings have been saved. Please check your model configuration.");
return Ok(());
}
match create("tetrate", model_config, Vec::new()).await {
match create("tetrate", Vec::new()).await {
Ok(provider) => {
let test_result = provider.fetch_supported_models().await;

View file

@ -3,8 +3,8 @@ use console::style;
use goose::config::paths::Paths;
use goose::config::Config;
use goose::conversation::message::Message;
use goose::providers::errors::ProviderError;
use goose::session::session_manager::{DB_NAME, SESSIONS_FOLDER};
use goose_providers::errors::ProviderError;
use serde_yaml;
use std::time::Duration;
@ -73,11 +73,10 @@ async fn check_provider(
}
};
let model_config = goose::model::ModelConfig::new(&model)
.map_err(|e| ProviderCheckError::InvalidModel(e.to_string()))?
.with_canonical_limits(&provider);
let model_config = goose::model_config::model_config_from_user_config(&provider, &model)
.map_err(|e| ProviderCheckError::InvalidModel(e.to_string()))?;
let provider_client = goose::providers::create(&provider, model_config, Vec::new())
let provider_client = goose::providers::create(&provider, Vec::new())
.await
.map_err(|e| {
let error = e.to_string();
@ -88,12 +87,13 @@ async fn check_provider(
})?;
let test_msg = Message::user().with_text("Say 'ok'");
let model_config = provider_client.get_model_config();
let start = std::time::Instant::now();
provider_client
.complete(&model_config, "check", "", &[test_msg], &[])
.await
.map_err(ProviderCheckError::ProviderRequest)?;
goose::session_context::with_session_id(
Some("check".to_string()),
provider_client.complete(&model_config, "", &[test_msg], &[]),
)
.await
.map_err(ProviderCheckError::ProviderRequest)?;
Ok(ProviderCheckSuccess {
provider,

View file

@ -8,7 +8,9 @@ pub mod recipe;
pub mod review;
pub mod schedule;
pub mod session;
pub mod skills;
pub mod term;
#[cfg(feature = "tui")]
pub mod tui;
#[cfg(feature = "update")]
pub mod update;

View file

@ -36,7 +36,7 @@ pub fn handle_project_default() -> Result<()> {
}
// Sort projects by last_accessed (newest first)
projects.sort_by(|a, b| b.last_accessed.cmp(&a.last_accessed));
projects.sort_by_key(|project| std::cmp::Reverse(project.last_accessed));
// Get the most recent project
let project = &projects[0];
@ -178,7 +178,7 @@ pub fn handle_projects_interactive() -> Result<()> {
}
// Sort projects by last_accessed (newest first)
projects.sort_by(|a, b| b.last_accessed.cmp(&a.last_accessed));
projects.sort_by_key(|project| std::cmp::Reverse(project.last_accessed));
// Format project paths for display
let project_choices: Vec<(String, String)> = projects

View file

@ -29,7 +29,9 @@ pub struct ReviewOptions {
/// Force every discovered check to run with this model, regardless of
/// the check's own `model:` field.
pub override_model: Option<String>,
/// Default `turn-limit` applied to checks that do not declare their own.
/// Default `turn-limit` for orchestrated main-pass subprocesses and for
/// checks that do not declare their own. Does not cap the legacy
/// `--no-orchestrate` in-process main agent.
pub default_turn_limit: Option<usize>,
/// Print the assembled prompt and discovered checks instead of dispatching
/// the review.

View file

@ -12,7 +12,8 @@
//!
//! - One subprocess per check (`goose run -q -t <prompt>`)
//! - Concurrency capped at [`MAX_WORKERS`] via a Tokio semaphore
//! - Per-check timeout of [`CHECK_TIMEOUT_SECS`]
//! - Per-subprocess turn limit via `--max-turns` (see
//! [`resolve_main_turn_limit`] and [`Check::resolved_turn_limit`])
//! - Each check is given a strict, tool-free prompt and is required to
//! return only `{"findings": [...]}` JSON
//! - Findings are tagged with the originating `check` name in Rust, not
@ -31,27 +32,19 @@ use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::process::Stdio;
use std::sync::Arc;
use std::time::Duration;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
use tokio::sync::Semaphore;
use tokio::task::JoinSet;
use tokio::time::timeout;
use super::handler::ReviewOptions;
use goose::checks::Check;
use goose::checks::{Check, DEFAULT_CHECK_TURN_LIMIT};
/// Maximum number of check subprocesses we run concurrently. 4 is
/// empirically the sweet spot before LLM-side rate limits and local
/// resource contention start hurting wall-clock.
pub const MAX_WORKERS: usize = 4;
/// Hard wall-clock cap for a single check subprocess. A check that
/// takes longer than this is almost always stuck in a tool-call loop
/// or a retry storm; we'd rather surface the timeout than block the
/// whole review.
pub const CHECK_TIMEOUT_SECS: u64 = 5 * 60;
/// One review finding emitted by a check or by the main correctness
/// pass.
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -87,7 +80,7 @@ struct RawFinding {
/// Run all discovered checks concurrently as `goose run` subprocesses.
///
/// Returns one `Vec<Finding>` per check, in the same order as `checks`.
/// A failed check (subprocess error, timeout, malformed JSON) yields an
/// A failed check (subprocess error, turn-limit exhaustion, malformed JSON) yields an
/// empty findings list and a warning on stderr; a single broken check
/// must never block the rest of the review.
pub async fn run_checks_in_parallel(
@ -186,6 +179,14 @@ fn resolve_check_model(check: &Check, opts: &ReviewOptions) -> Option<String> {
opts.default_model.clone()
}
/// Resolve the turn limit for a main-pass subprocess.
///
/// Uses `goose review --turn-limit` when set, otherwise
/// [`DEFAULT_CHECK_TURN_LIMIT`].
fn resolve_main_turn_limit(default_turn_limit: Option<usize>) -> usize {
default_turn_limit.unwrap_or(DEFAULT_CHECK_TURN_LIMIT)
}
/// Spawn a single `goose run` subprocess for one check and parse its
/// output into [`Finding`]s.
async fn run_single_check_subprocess(
@ -196,7 +197,8 @@ async fn run_single_check_subprocess(
instructions: Option<&str>,
max_turns: Option<usize>,
) -> Result<Vec<Finding>> {
let prompt = build_check_prompt(check, diff, instructions);
let turns = max_turns.expect("check subprocess always has a resolved turn limit");
let prompt = build_check_prompt(check, diff, instructions, turns);
let raw = run_subprocess_for_findings(
&prompt,
&format!("check '{}'", check.name),
@ -222,8 +224,7 @@ async fn run_single_check_subprocess(
/// Generic `goose run` subprocess that hands a prompt to the model
/// and parses `{"findings": [...]}` JSON out of the response. Shared
/// by the per-check and per-file main-pass orchestrators so both get
/// the same robust JSON extraction, timeout handling, and error
/// reporting.
/// the same robust JSON extraction and error reporting.
async fn run_subprocess_for_findings(
prompt: &str,
label: &str,
@ -243,11 +244,8 @@ async fn run_subprocess_for_findings(
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
// Drop-on-cancel safety: when the outer `timeout` fires it drops
// this future, which drops the Child handle. Without
// kill_on_drop, the Tokio runtime leaves the subprocess running
// (and racking up tokens) in the background — kill_on_drop sends
// SIGKILL on Drop instead.
// If this future is dropped, kill the child so it does not keep
// running (and racking up tokens) in the background.
.kill_on_drop(true);
if let Some(p) = provider {
@ -273,13 +271,10 @@ async fn run_subprocess_for_findings(
drop(stdin);
}
let wait = child.wait_with_output();
let output = match timeout(Duration::from_secs(CHECK_TIMEOUT_SECS), wait).await {
Ok(o) => o.with_context(|| format!("wait on {label}"))?,
Err(_) => {
anyhow::bail!("{label} timed out after {}s", CHECK_TIMEOUT_SECS);
}
};
let output = child
.wait_with_output()
.await
.with_context(|| format!("wait on {label}"))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
@ -321,6 +316,7 @@ pub async fn run_main_pass_in_parallel(
let semaphore = Arc::new(Semaphore::new(MAX_WORKERS));
let mut set: JoinSet<(usize, String, Result<Vec<RawFinding>>, bool)> = JoinSet::new();
let max_turns = resolve_main_turn_limit(opts.default_turn_limit);
for (idx, (path, file_diff)) in per_file.iter().enumerate() {
let sem = semaphore.clone();
@ -334,15 +330,20 @@ pub async fn run_main_pass_in_parallel(
set.spawn(async move {
let _permit = sem.acquire().await.expect("semaphore is never closed");
let prompt =
build_main_pass_prompt(&path, &file_diff, &base_prompt, instructions.as_deref());
let prompt = build_main_pass_prompt(
&path,
&file_diff,
&base_prompt,
instructions.as_deref(),
max_turns,
);
let label = format!("main:{path}");
let result = run_subprocess_for_findings(
&prompt,
&label,
provider.as_deref(),
model.as_deref(),
None,
Some(max_turns),
)
.await;
(idx, path, result, quiet)
@ -567,6 +568,22 @@ fn take_quoted(s: &str) -> Option<(String, &str)> {
None
}
/// Prompt section telling review subprocesses about the `--max-turns`
/// cap enforced by goose. Without this, models routinely burn turns on
/// tool loops and return nothing when the limit stops the session.
fn build_subprocess_turn_budget_section(max_turns: usize) -> String {
format!(
"## Turn budget\n\n\
You may take at most {max_turns} agent turns (model/tool iterations) in this run. \
goose enforces this via `--max-turns`; when you exhaust it, the session stops and \
any findings not yet emitted as JSON are lost.\n\n\
Plan for the limit:\n\
- As turns run low, stop exploring and return JSON with the findings you have verified.\n\
- Always emit valid JSON (`{{\"findings\":[...]}}` or `{{\"findings\":[]}}`) before \
the turn limit an empty or missing response counts as failure.\n\n"
)
}
/// Build the strict, JSON-only prompt sent to one main-pass
/// subprocess. The base prompt (custom or
/// [`DEFAULT_REVIEW_PROMPT`]) supplies the reviewer voice; we then
@ -577,6 +594,7 @@ fn build_main_pass_prompt(
file_diff: &str,
base_prompt: &str,
instructions: Option<&str>,
max_turns: usize,
) -> String {
let mut s = String::new();
s.push_str(base_prompt.trim_end());
@ -589,6 +607,7 @@ fn build_main_pass_prompt(
s.push_str("\n\n");
}
}
s.push_str(&build_subprocess_turn_budget_section(max_turns));
s.push_str("## File under review\n\n");
s.push_str(&format!("Path: `{path}`\n\n"));
s.push_str(
@ -611,7 +630,12 @@ fn build_main_pass_prompt(
/// Shape matches the prompt format Amp-authored checks already expect,
/// so a check written for `amp review` runs the same way under
/// `goose review`.
fn build_check_prompt(check: &Check, diff: &str, instructions: Option<&str>) -> String {
fn build_check_prompt(
check: &Check,
diff: &str,
instructions: Option<&str>,
max_turns: usize,
) -> String {
let mut s = String::new();
s.push_str("You are running an automated code review check.\n\n");
s.push_str(&format!("Check name: {}\n", check.name));
@ -633,7 +657,9 @@ fn build_check_prompt(check: &Check, diff: &str, instructions: Option<&str>) ->
s.push('\n');
}
}
s.push_str("\nReview ONLY the git diff provided below.\n");
s.push('\n');
s.push_str(&build_subprocess_turn_budget_section(max_turns));
s.push_str("Review ONLY the git diff provided below.\n");
s.push_str("Do not ask for missing context.\n");
s.push_str("Use repo-relative file paths.\n");
s.push_str("Use post-change line numbers from the diff.\n");
@ -820,7 +846,7 @@ mod tests {
#[test]
fn check_prompt_is_strict_and_diff_aware() {
let p = build_check_prompt(&ck("perf"), "diff content", None);
let p = build_check_prompt(&ck("perf"), "diff content", None, DEFAULT_CHECK_TURN_LIMIT);
assert!(p.contains("automated code review check"));
assert!(p.contains("Check name: perf"));
assert!(p.contains("```diff\ndiff content\n```"));
@ -833,7 +859,7 @@ mod tests {
fn check_prompt_restricts_findings_to_added_or_modified_lines() {
// Mirrors Amp's prompt language; without these the model
// happily flags pre-existing code shown for context.
let p = build_check_prompt(&ck("perf"), "diff content", None);
let p = build_check_prompt(&ck("perf"), "diff content", None, DEFAULT_CHECK_TURN_LIMIT);
assert!(p.contains("ONLY in the changed lines"));
assert!(p.contains("lines beginning with `+`"));
assert!(p.contains("ONLY for code that was added or modified"));
@ -846,6 +872,7 @@ mod tests {
&ck("perf"),
"diff content",
Some("This is a refactor; flag any behavior change."),
DEFAULT_CHECK_TURN_LIMIT,
);
assert!(p.contains("Reviewer instructions:"));
assert!(p.contains("flag any behavior change"));
@ -853,10 +880,29 @@ mod tests {
#[test]
fn check_prompt_skips_blank_reviewer_instructions() {
let p = build_check_prompt(&ck("perf"), "diff content", Some(" \n "));
let p = build_check_prompt(
&ck("perf"),
"diff content",
Some(" \n "),
DEFAULT_CHECK_TURN_LIMIT,
);
assert!(!p.contains("Reviewer instructions"));
}
#[test]
fn check_prompt_includes_turn_budget() {
let p = build_check_prompt(&ck("perf"), "diff content", None, 12);
assert!(p.contains("## Turn budget"));
assert!(p.contains("at most 12 agent turns"));
assert!(p.contains("--max-turns"));
}
#[test]
fn resolve_main_turn_limit_uses_cli_default_or_fallback() {
assert_eq!(resolve_main_turn_limit(Some(40)), 40);
assert_eq!(resolve_main_turn_limit(None), DEFAULT_CHECK_TURN_LIMIT);
}
#[test]
fn parse_findings_accepts_bare_json() {
let raw = r#"{"findings":[{"severity":"high","path":"a.py","line_start":1,"line_end":2,"summary":"bad"}]}"#;
@ -1090,6 +1136,7 @@ rename to new/name.rs
"diff --git a/src/foo.rs b/src/foo.rs\n@@ -1 +1 @@\n-old\n+new\n",
"BASE PROMPT",
None,
DEFAULT_CHECK_TURN_LIMIT,
);
assert!(p.starts_with("BASE PROMPT"));
assert!(p.contains("Path: `src/foo.rs`"));
@ -1108,6 +1155,7 @@ rename to new/name.rs
"diff body",
"BASE",
Some("PR is a refactor; flag behavior changes."),
DEFAULT_CHECK_TURN_LIMIT,
);
assert!(p.contains("## Reviewer instructions"));
assert!(p.contains("flag behavior changes"));
@ -1115,7 +1163,20 @@ rename to new/name.rs
#[test]
fn main_pass_prompt_skips_blank_reviewer_instructions() {
let p = build_main_pass_prompt("src/foo.rs", "diff body", "BASE", Some(" \n \t\n"));
let p = build_main_pass_prompt(
"src/foo.rs",
"diff body",
"BASE",
Some(" \n \t\n"),
DEFAULT_CHECK_TURN_LIMIT,
);
assert!(!p.contains("Reviewer instructions"));
}
#[test]
fn main_pass_prompt_includes_turn_budget() {
let p = build_main_pass_prompt("src/foo.rs", "diff body", "BASE", None, 18);
assert!(p.contains("## Turn budget"));
assert!(p.contains("at most 18 agent turns"));
}
}

View file

@ -217,11 +217,11 @@ pub async fn handle_schedule_sessions(schedule_id: String, limit: Option<usize>)
println!("No sessions found for schedule ID '{}'.", schedule_id);
} else {
println!("Sessions for schedule ID '{}':", schedule_id);
// sessions is now Vec<(String, SessionMetadata)>
for (session_name, metadata) in sessions {
println!(
" - Session ID: {}, Working Dir: {}, Description: \"{}\", Schedule ID: {:?}",
session_name, // Display the session_name as Session ID
" - Session ID: {}, Messages: {}, Working Dir: {}, Description: \"{}\", Schedule ID: {:?}",
session_name,
metadata.message_count,
metadata.working_dir.display(),
metadata.name,
metadata.schedule_id.as_deref().unwrap_or("N/A")

View file

@ -7,7 +7,9 @@ use etcetera::home_dir;
use goose::config::Config;
#[cfg(feature = "nostr")]
use goose::session::nostr_share;
use goose::session::{generate_diagnostics, Session, SessionManager, SessionType};
use goose::session::{
generate_diagnostics, DiagnosticsLevel, Session, SessionManager, SessionType,
};
use goose::utils::safe_truncate;
use regex::Regex;
use std::fs;
@ -68,7 +70,8 @@ fn prompt_interactive_session_removal(sessions: &[Session]) -> Result<Vec<Sessio
&s.name
};
let truncated_desc = safe_truncate(desc, TRUNCATED_DESC_LENGTH);
let display_text = format!("{} - {} ({})", s.updated_at, truncated_desc, s.id);
let display_text =
format!("{} - {} ({})", session_activity_at(s), truncated_desc, s.id);
(display_text, s.clone())
})
.collect();
@ -148,6 +151,10 @@ fn write_line_or_broken_pipe_ok<W: Write>(out: &mut W, line: &str) -> Result<boo
}
}
fn session_activity_at(session: &Session) -> chrono::DateTime<chrono::Utc> {
session.last_message_at.unwrap_or(session.updated_at)
}
pub async fn handle_session_list(
format: String,
ascending: bool,
@ -168,9 +175,9 @@ pub async fn handle_session_list(
}
if ascending {
sessions.sort_by(|a, b| a.updated_at.cmp(&b.updated_at));
sessions.sort_by_key(session_activity_at);
} else {
sessions.sort_by(|a, b| b.updated_at.cmp(&a.updated_at));
sessions.sort_by_key(|b| std::cmp::Reverse(session_activity_at(b)));
}
if let Some(n) = limit {
@ -204,7 +211,7 @@ pub async fn handle_session_list(
"{} - {} - {} - {}",
session.id,
session.name,
session.updated_at,
session_activity_at(&session),
display_path_with_tilde(&session.working_dir)
);
if !write_line_or_broken_pipe_ok(&mut out, &output)? {
@ -300,6 +307,15 @@ pub async fn handle_session_import(input: String, nostr: bool) -> Result<()> {
.with_context(|| format!("Failed to read session import file: {input}"))?
};
let format = goose::session::import_formats::detect_format(&json);
let label = match format {
goose::session::import_formats::ImportFormat::Goose => "goose",
goose::session::import_formats::ImportFormat::ClaudeCode => "Claude Code",
goose::session::import_formats::ImportFormat::Codex => "Codex",
goose::session::import_formats::ImportFormat::Pi => "Pi",
};
println!("Detected format: {}", label);
let session_manager = SessionManager::instance();
let session = session_manager
.import_session(&json, Some(SessionType::User))
@ -313,24 +329,27 @@ pub async fn handle_session_import(input: String, nostr: bool) -> Result<()> {
pub async fn handle_diagnostics(session_id: &str, output_path: Option<PathBuf>) -> Result<()> {
println!(
"Generating diagnostics bundle for session '{}'...",
"Generating diagnostics report for session '{}'...",
session_id
);
let session_manager = SessionManager::instance();
let diagnostics_data = generate_diagnostics(&session_manager, session_id)
.await
.with_context(|| {
format!(
"Failed to write to generate diagnostics bundle for session '{}'",
session_id
)
})?;
let diagnostics_report =
generate_diagnostics(&session_manager, session_id, DiagnosticsLevel::Full)
.await
.with_context(|| {
format!(
"Failed to generate diagnostics report for session '{}'",
session_id
)
})?;
let diagnostics_data = serde_json::to_vec_pretty(&diagnostics_report)
.context("Failed to serialize diagnostics report")?;
let output_file = if let Some(path) = output_path {
path.clone()
} else {
PathBuf::from(format!("diagnostics_{}.zip", session_id))
PathBuf::from(format!("diagnostics_{}.json", session_id))
};
let mut file = fs::File::create(&output_file).context(format!(
@ -341,7 +360,7 @@ pub async fn handle_diagnostics(session_id: &str, output_path: Option<PathBuf>)
file.write_all(&diagnostics_data)
.context("Failed to write diagnostics data")?;
println!("Diagnostics bundle saved to: {}", output_file.display());
println!("Diagnostics report saved to: {}", output_file.display());
Ok(())
}

View file

@ -0,0 +1,228 @@
use anyhow::Result;
use console::{measure_text_width, Term};
use goose::skills::list_installed_skills;
use goose::token_counter::create_token_counter;
const DESCRIPTION_PREVIEW_CHARS: usize = 50;
const SEPARATOR: &str = " | ";
const MIN_DESCRIPTION_WIDTH: usize = 4;
const MIN_LOCATION_WIDTH: usize = 4;
const NAME_HEADER: &str = "Name";
const DESCRIPTION_HEADER: &str = "Description";
const DESCRIPTION_TOKENS_HEADER: &str = "Description tokens";
const CONTENT_TOKENS_HEADER: &str = "Content tokens";
const LOCATION_HEADER: &str = "Location";
struct SkillRow {
name: String,
description: String,
description_tokens: String,
content_tokens: String,
location: String,
}
struct ColumnWidths {
name: usize,
description: usize,
description_tokens: usize,
content_tokens: usize,
location: usize,
}
pub async fn handle_skills_list() -> Result<()> {
let cwd = std::env::current_dir()?;
let terminal_width = terminal_width();
let token_counter = create_token_counter().await.map_err(anyhow::Error::msg)?;
let mut skills = list_installed_skills(Some(&cwd));
skills.sort_by(|a, b| a.name.cmp(&b.name));
let rows = skills
.iter()
.map(|skill| SkillRow {
name: skill.name.clone(),
description: description_preview(&skill.description),
description_tokens: token_counter.count_tokens(&skill.description).to_string(),
content_tokens: token_counter.count_tokens(&skill.content).to_string(),
location: skill.path.clone(),
})
.collect::<Vec<_>>();
let widths = column_widths(&rows, terminal_width);
println!("{}", header_line(&widths, terminal_width));
for row in rows {
println!("{}", skill_line(&row, &widths, terminal_width));
}
Ok(())
}
fn terminal_width() -> Option<usize> {
Term::stdout()
.size_checked()
.map(|(_height, width)| width as usize)
}
fn column_widths(rows: &[SkillRow], max_display_width: Option<usize>) -> ColumnWidths {
let description_tokens = max_width(
DESCRIPTION_TOKENS_HEADER,
rows.iter().map(|row| row.description_tokens.as_str()),
);
let content_tokens = max_width(
CONTENT_TOKENS_HEADER,
rows.iter().map(|row| row.content_tokens.as_str()),
);
let longest_name = max_width(NAME_HEADER, rows.iter().map(|row| row.name.as_str()));
let description = max_width(
DESCRIPTION_HEADER,
rows.iter().map(|row| row.description.as_str()),
);
let location = max_width(
LOCATION_HEADER,
rows.iter().map(|row| row.location.as_str()),
);
let Some(width) = max_display_width else {
return ColumnWidths {
name: longest_name,
description,
description_tokens,
content_tokens,
location,
};
};
let separator_width = measure_text_width(SEPARATOR) * 4;
let available_width = width.saturating_sub(separator_width);
let dynamic_width = available_width.saturating_sub(description_tokens + content_tokens);
let name =
longest_name.min(dynamic_width.saturating_sub(MIN_DESCRIPTION_WIDTH + MIN_LOCATION_WIDTH));
let remaining_after_name = dynamic_width.saturating_sub(name);
let description = description.min(remaining_after_name.saturating_sub(MIN_LOCATION_WIDTH));
let remaining_after_description = remaining_after_name.saturating_sub(description);
let location = location.min(remaining_after_description);
ColumnWidths {
name,
description,
description_tokens,
content_tokens,
location,
}
}
fn max_width<'a>(header: &str, values: impl Iterator<Item = &'a str>) -> usize {
values
.map(measure_text_width)
.chain(std::iter::once(measure_text_width(header)))
.max()
.unwrap_or(0)
}
fn header_line(widths: &ColumnWidths, max_display_width: Option<usize>) -> String {
let line = format_line(
NAME_HEADER,
DESCRIPTION_HEADER,
DESCRIPTION_TOKENS_HEADER,
CONTENT_TOKENS_HEADER,
LOCATION_HEADER,
widths,
);
match max_display_width {
Some(width) => truncate_to_display_width(&line, width),
None => line,
}
}
fn skill_line(row: &SkillRow, widths: &ColumnWidths, max_display_width: Option<usize>) -> String {
let line = format_line(
&row.name,
&row.description,
&row.description_tokens,
&row.content_tokens,
&row.location,
widths,
);
match max_display_width {
Some(width) => truncate_to_display_width(&line, width),
None => line,
}
}
fn format_line(
name: &str,
description: &str,
description_tokens: &str,
content_tokens: &str,
location: &str,
widths: &ColumnWidths,
) -> String {
format!(
"{}{}{}{}{}{}{}{}{}",
pad_to_display_width(&truncate_to_display_width(name, widths.name), widths.name),
SEPARATOR,
pad_to_display_width(
&truncate_to_display_width(description, widths.description),
widths.description
),
SEPARATOR,
pad_to_display_width(description_tokens, widths.description_tokens),
SEPARATOR,
pad_to_display_width(content_tokens, widths.content_tokens),
SEPARATOR,
pad_to_display_width(
&truncate_to_display_width(location, widths.location),
widths.location
),
)
}
fn description_preview(description: &str) -> String {
let normalized = description.split_whitespace().collect::<Vec<_>>().join(" ");
truncate_to_chars(&normalized, DESCRIPTION_PREVIEW_CHARS)
}
fn truncate_to_chars(text: &str, max_chars: usize) -> String {
if text.chars().count() <= max_chars {
return text.to_string();
}
if max_chars <= 3 {
return ".".repeat(max_chars);
}
let mut output = text.chars().take(max_chars - 3).collect::<String>();
output.push_str("...");
output
}
fn truncate_to_display_width(text: &str, max_width: usize) -> String {
if measure_text_width(text) <= max_width {
return text.to_string();
}
if max_width <= 3 {
return ".".repeat(max_width);
}
let mut output = String::new();
let suffix_width = measure_text_width("...");
for ch in text.chars() {
output.push(ch);
if measure_text_width(&output) + suffix_width > max_width {
output.pop();
break;
}
}
output.push_str("...");
output
}
fn pad_to_display_width(text: &str, width: usize) -> String {
let padding = width.saturating_sub(measure_text_width(text));
format!("{}{}", text, " ".repeat(padding))
}

View file

@ -280,9 +280,15 @@ pub async fn handle_term_run(prompt: Vec<String>) -> Result<()> {
};
if let Some(oldest_user) = user_messages_after_last_assistant.last() {
session_manager
.truncate_conversation(&session_id, oldest_user.created)
.await?;
if let Some(message_id) = oldest_user.id.as_deref() {
session_manager
.truncate_conversation_from_message(&session_id, message_id)
.await?;
} else {
session_manager
.truncate_conversation(&session_id, oldest_user.created)
.await?;
}
}
let prompt_with_context = if user_messages_after_last_assistant.is_empty() {
@ -324,7 +330,10 @@ pub async fn handle_term_info() -> Result<()> {
let session_manager = SessionManager::instance();
let session = session_manager.get_session(&session_id, false).await.ok();
let total_tokens = session.as_ref().and_then(|s| s.total_tokens).unwrap_or(0) as usize;
let total_tokens = session
.as_ref()
.and_then(|s| s.usage.total_tokens)
.unwrap_or(0) as usize;
let config = goose::config::Config::global();
let model_name = config
@ -345,9 +354,7 @@ pub async fn handle_term_info() -> Result<()> {
.ok()
.and_then(|model_name| {
config.get_goose_provider().ok().and_then(|provider_name| {
goose::model::ModelConfig::new(&model_name)
.ok()
.map(|c| c.with_canonical_limits(&provider_name))
goose::model_config::model_config_from_user_config(&provider_name, &model_name).ok()
})
})
.map(|mc| mc.context_limit())

View file

@ -1,4 +1,8 @@
use anyhow::{bail, Context, Result};
use reqwest::{
header::{HeaderValue, AUTHORIZATION},
StatusCode,
};
use sha2::{Digest, Sha256};
use sigstore_verify::trust_root::{TrustedRoot, SIGSTORE_PRODUCTION_TRUSTED_ROOT};
use sigstore_verify::types::{Bundle, Sha256Hash};
@ -64,7 +68,7 @@ fn binary_name() -> &'static str {
fn sha256_hex(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
format!("{:x}", hasher.finalize())
goose::utils::bytes_to_hex(hasher.finalize())
}
#[derive(serde::Deserialize)]
@ -79,6 +83,32 @@ struct AttestationEntry {
const GITHUB_ACTIONS_ISSUER: &str = "https://token.actions.githubusercontent.com";
fn sanitized_token(token: Option<&str>) -> Option<&str> {
token.map(str::trim).filter(|tok| !tok.is_empty())
}
fn authorization_header_value(token: &str) -> Option<HeaderValue> {
HeaderValue::from_str(&format!("Bearer {token}")).ok()
}
fn github_token() -> Option<String> {
env::var("GITHUB_TOKEN")
.ok()
.and_then(|tok| sanitized_token(Some(&tok)).map(str::to_owned))
.or_else(|| {
env::var("GH_TOKEN")
.ok()
.and_then(|tok| sanitized_token(Some(&tok)).map(str::to_owned))
})
}
fn should_retry_attestations_without_token(status: StatusCode, token: Option<&str>) -> bool {
sanitized_token(token)
.and_then(authorization_header_value)
.is_some()
&& matches!(status, StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN)
}
async fn fetch_attestations(digest: &str, token: Option<&str>) -> Result<Vec<serde_json::Value>> {
let url = format!(
"https://api.github.com/repos/aaif-goose/goose/attestations/sha256:{digest}\
@ -86,17 +116,14 @@ async fn fetch_attestations(digest: &str, token: Option<&str>) -> Result<Vec<ser
);
let client = reqwest::Client::new();
let mut req = client
.get(&url)
.header("Accept", "application/vnd.github+json")
.header("X-GitHub-Api-Version", "2022-11-28")
.header("User-Agent", "goose-cli");
let token = sanitized_token(token);
let resp = fetch_attestations_response(&client, &url, token).await?;
if let Some(tok) = token {
req = req.header("Authorization", format!("Bearer {tok}"));
}
let resp = req.send().await.context("Failed to fetch attestations")?;
let resp = if should_retry_attestations_without_token(resp.status(), token) {
fetch_attestations_response(&client, &url, None).await?
} else {
resp
};
if !resp.status().is_success() {
bail!("GitHub attestation API returned HTTP {}", resp.status());
@ -110,6 +137,24 @@ async fn fetch_attestations(digest: &str, token: Option<&str>) -> Result<Vec<ser
Ok(body.attestations.into_iter().map(|a| a.bundle).collect())
}
async fn fetch_attestations_response(
client: &reqwest::Client,
url: &str,
token: Option<&str>,
) -> Result<reqwest::Response> {
let mut req = client
.get(url)
.header("Accept", "application/vnd.github+json")
.header("X-GitHub-Api-Version", "2022-11-28")
.header("User-Agent", "goose-cli");
if let Some(value) = token.and_then(authorization_header_value) {
req = req.header(AUTHORIZATION, value);
}
req.send().await.context("Failed to fetch attestations")
}
// Verify a single attestation bundle against the artifact digest and workflow.
fn verify_bundle(
bundle_json: &serde_json::Value,
@ -142,8 +187,8 @@ fn verify_bundle(
Ok(())
}
/// Returns `Ok(true)` verified, `Ok(false)` skipped (soft warning), `Err` hard failure.
async fn verify_provenance(archive_data: &[u8], tag: &str) -> Result<bool> {
/// Returns `Ok(())` when the downloaded archive has verified provenance.
async fn verify_provenance(archive_data: &[u8], tag: &str) -> Result<()> {
let digest = sha256_hex(archive_data);
println!("Archive SHA-256: {digest}");
@ -152,30 +197,19 @@ async fn verify_provenance(archive_data: &[u8], tag: &str) -> Result<bool> {
_ => "release.yml",
};
let token = env::var("GITHUB_TOKEN")
.ok()
.or_else(|| env::var("GH_TOKEN").ok());
let token = github_token();
println!("Verifying SLSA provenance via Sigstore...");
let bundles = match fetch_attestations(&digest, token.as_deref()).await {
Ok(b) if b.is_empty() => {
eprintln!(
"Warning: No Sigstore attestation found for this build. \
This may be expected for canary or nightly builds."
);
return Ok(false);
}
Ok(b) => b,
Err(e) => {
eprintln!(
"Warning: Sigstore provenance check could not complete: {e}\n\
This may be expected for releases published before provenance \
attestations were enabled."
);
return Ok(false);
}
};
let bundles = fetch_attestations(&digest, token.as_deref())
.await
.context(
"Sigstore provenance check could not complete; refusing to install unverifiable update",
)?;
if bundles.is_empty() {
bail!("No Sigstore attestation found for downloaded archive; refusing to install unverifiable update");
}
let trusted_root = TrustedRoot::from_json(SIGSTORE_PRODUCTION_TRUSTED_ROOT)
.context("Failed to load Sigstore trusted root")?;
@ -195,7 +229,7 @@ async fn verify_provenance(archive_data: &[u8], tag: &str) -> Result<bool> {
) {
Ok(()) => {
println!("Sigstore provenance verification passed.");
return Ok(true);
return Ok(());
}
Err(e) => last_err = Some(e),
}
@ -247,7 +281,7 @@ pub async fn update(canary: bool, reconfigure: bool) -> Result<()> {
println!("Downloaded {} bytes.", bytes.len());
// --- Verify SLSA provenance via Sigstore --------------------------------
let provenance_verified = verify_provenance(&bytes, tag).await?;
verify_provenance(&bytes, tag).await?;
// --- Extract to temp dir (hardened against path traversal) --------------
let tmp_dir = tempfile::tempdir().context("Failed to create temp directory")?;
@ -274,11 +308,7 @@ pub async fn update(canary: bool, reconfigure: bool) -> Result<()> {
#[cfg(target_os = "windows")]
copy_dlls(&extracted_binary, &current_exe)?;
if provenance_verified {
println!("goose updated successfully (verified with Sigstore SLSA provenance).");
} else {
println!("goose updated successfully.");
}
println!("goose updated successfully (verified with Sigstore SLSA provenance).");
// --- Reconfigure if requested -------------------------------------------
if reconfigure {
@ -737,6 +767,48 @@ mod tests {
);
}
#[test]
fn test_sanitized_token_trims_blank_values() {
assert_eq!(sanitized_token(None), None);
assert_eq!(sanitized_token(Some("")), None);
assert_eq!(sanitized_token(Some(" ")), None);
assert_eq!(sanitized_token(Some(" token\n")), Some("token"));
}
#[test]
fn test_authorization_header_value_rejects_malformed_tokens() {
assert!(authorization_header_value("token").is_some());
assert!(authorization_header_value("bad\ntoken").is_none());
}
#[test]
fn test_attestation_lookup_retries_auth_failures_without_token() {
assert!(should_retry_attestations_without_token(
StatusCode::UNAUTHORIZED,
Some("token")
));
assert!(should_retry_attestations_without_token(
StatusCode::FORBIDDEN,
Some("token")
));
assert!(!should_retry_attestations_without_token(
StatusCode::INTERNAL_SERVER_ERROR,
Some("token")
));
assert!(!should_retry_attestations_without_token(
StatusCode::UNAUTHORIZED,
Some("")
));
assert!(!should_retry_attestations_without_token(
StatusCode::UNAUTHORIZED,
Some("bad\ntoken")
));
assert!(!should_retry_attestations_without_token(
StatusCode::UNAUTHORIZED,
None
));
}
// -----------------------------------------------------------------------
// Path validation and extraction hardening tests
// -----------------------------------------------------------------------
@ -808,13 +880,11 @@ mod tests {
// -----------------------------------------------------------------------
#[tokio::test]
async fn test_verify_provenance_warns_on_missing_attestation() {
async fn test_verify_provenance_fails_closed_when_unverifiable() {
let result = verify_provenance(b"not a real archive", "stable").await;
// Network failures and missing attestations are soft warnings: Ok(false), not hard errors.
assert_eq!(
result.ok(),
Some(false),
"verify_provenance should return Ok(false) when attestations cannot be fetched"
assert!(
result.is_err(),
"verify_provenance must fail closed when provenance cannot be verified"
);
}

View file

@ -1,3 +1,5 @@
#![recursion_limit = "256"]
#[cfg(not(any(feature = "rustls-tls", feature = "native-tls")))]
compile_error!("At least one of `rustls-tls` or `native-tls` features must be enabled");

View file

@ -1,53 +1,31 @@
use anyhow::Result;
use std::sync::Once;
use goose::providers::utils::init_goose_request_log;
use std::sync::OnceLock;
// Used to ensure we only set up tracing once
static INIT: Once = Once::new();
static INIT: OnceLock<Result<()>> = OnceLock::new();
/// Sets up the logging infrastructure for the CLI.
/// Logs go to a JSON file only (no console output).
pub fn setup_logging(name: Option<&str>) -> Result<()> {
setup_logging_internal(name, false)
}
pub fn setup_logging(name: Option<&str>) -> &'static Result<()> {
INIT.get_or_init(|| {
use tracing_subscriber::util::SubscriberInitExt;
fn setup_logging_internal(name: Option<&str>, force: bool) -> Result<()> {
let mut result = Ok(());
init_goose_request_log()?;
let config = goose::logging::LoggingConfig {
component: "cli",
name,
extra_directives: &["goose_cli=info"],
console: false,
json: true,
};
let subscriber = goose::logging::build_logging_subscriber(&config)?;
let mut setup = || {
result = (|| {
use tracing_subscriber::util::SubscriberInitExt;
let config = goose::logging::LoggingConfig {
component: "cli",
name,
extra_directives: &["goose_cli=info"],
console: false,
json: true,
};
let subscriber = goose::logging::build_logging_subscriber(&config)?;
if force {
let _guard = subscriber.set_default();
tracing::warn!("Test log entry from setup");
tracing::info!("Another test log entry from setup");
std::thread::sleep(std::time::Duration::from_millis(100));
Ok(())
} else {
subscriber
.try_init()
.map_err(|e| anyhow::anyhow!("Failed to set global subscriber: {}", e))?;
Ok(())
}
})();
};
if force {
setup();
} else {
INIT.call_once(setup);
}
result
subscriber
.try_init()
.map_err(|e| anyhow::anyhow!("Failed to set global subscriber: {}", e))?;
Ok(())
})
}
#[cfg(test)]

View file

@ -1,3 +1,5 @@
#![recursion_limit = "256"]
use anyhow::Result;
use goose_cli::cli::cli;

View file

@ -178,6 +178,7 @@ mod tests {
envs: Envs::new(HashMap::new()),
env_keys: vec!["SLACK_TOKEN".to_string()],
timeout: None,
cwd: None,
description: "slack-mcp".to_string(),
bundled: None,
available_tools: Vec::new(),
@ -275,6 +276,7 @@ mod tests {
envs: Envs::new(HashMap::new()),
env_keys: vec!["API_KEY".to_string()], // Same original key, different extension
timeout: None,
cwd: None,
description: "service-b".to_string(),
bundled: None,
available_tools: Vec::new(),

View file

@ -9,7 +9,6 @@ use anyhow::Result;
use goose::agents::{Agent, AgentConfig, GoosePlatform};
use goose::config::permission::PermissionManager;
use goose::config::GooseMode;
use goose::model::ModelConfig;
use goose::providers::{create, testprovider::TestProvider};
use goose::session::session_manager::SessionType;
use goose::session::SessionManager;
@ -186,12 +185,7 @@ where
let original_env = setup_environment(config)?;
let inner_provider = create(
&factory_name,
ModelConfig::new(config.model_name)?.with_canonical_limits(&factory_name),
Vec::new(),
)
.await?;
let inner_provider = create(&factory_name, Vec::new()).await?;
let test_provider = Arc::new(TestProvider::new_recording(inner_provider, &file_path));
(
@ -246,9 +240,12 @@ where
)
.await?;
let scenario_model_config =
goose::model_config::model_config_from_user_config(&factory_name, config.model_name)?;
agent
.update_provider(
provider_arc as Arc<dyn goose::providers::base::Provider>,
scenario_model_config,
&session.id,
)
.await?;
@ -262,6 +259,7 @@ where
None,
None,
"text".to_string(),
false,
)
.await;

View file

@ -80,7 +80,7 @@ mod tests {
// run_scenario(
// "context_length_exceeded",
// Box::new(|provider| {
// let model_config = provider.get_model_config();
// let model_config = resolve_global_model_config();
// let context_length = model_config.context_limit.unwrap_or(300_000);
// // "hello " is only one token in most models, since the hello and space often
// // occur together in the training data.

View file

@ -6,6 +6,7 @@ use console::style;
use goose::agents::{Agent, Container, ExtensionError};
use goose::config::resolve_extensions_for_new_session;
use goose::config::{Config, ExtensionConfig, GooseMode};
use goose::model_config::model_config_from_user_config;
use goose::providers::create;
use goose::recipe::Recipe;
use goose::session::session_manager::SessionType;
@ -116,6 +117,8 @@ pub struct SessionBuilderConfig {
pub output_format: String,
/// Docker container to run stdio extensions inside
pub container: Option<Container>,
/// Print generation statistics after headless runs.
pub stats: bool,
}
/// Manual implementation of Default to ensure proper initialization of output_format
@ -143,6 +146,7 @@ impl Default for SessionBuilderConfig {
quiet: false,
output_format: "text".to_string(),
container: None,
stats: false,
}
}
}
@ -226,14 +230,14 @@ async fn load_extensions(
struct ResolvedProviderConfig {
provider_name: String,
model_name: String,
model_config: goose::model::ModelConfig,
model_config: goose_providers::model::ModelConfig,
}
fn resolve_provider_and_model(
session_config: &SessionBuilderConfig,
config: &Config,
saved_provider: Option<String>,
saved_model_config: Option<goose::model::ModelConfig>,
saved_model_config: Option<goose_providers::model::ModelConfig>,
) -> ResolvedProviderConfig {
let recipe_settings = session_config
.recipe
@ -274,14 +278,16 @@ fn resolve_provider_and_model(
}
config
} else {
let temperature = recipe_settings.and_then(|s| s.temperature);
goose::model::ModelConfig::new(&model_name)
.unwrap_or_else(|e| {
output::render_error(&format!("Failed to create model configuration: {}", e));
process::exit(1);
})
.with_canonical_limits(&provider_name)
.with_temperature(temperature)
let mut config =
goose::model_config::model_config_from_user_config(&provider_name, &model_name)
.unwrap_or_else(|e| {
output::render_error(&format!("Failed to create model configuration: {}", e));
process::exit(1);
});
if let Some(temp) = recipe_settings.and_then(|s| s.temperature) {
config = config.with_temperature(Some(temp));
}
config
};
ResolvedProviderConfig {
@ -327,7 +333,10 @@ async fn resolve_session_id(
}
}
} else {
match session_manager.list_sessions().await {
match session_manager
.list_sessions_by_types(&[SessionType::User])
.await
{
Ok(sessions) if !sessions.is_empty() => sessions[0].id.clone(),
_ => {
output::render_error("Cannot resume - no previous sessions found");
@ -408,6 +417,7 @@ async fn collect_extension_configs(
recipe: Option<&Recipe>,
session_id: &str,
) -> Result<Vec<ExtensionConfig>, ExtensionError> {
let recipe_extensions = recipe.and_then(|r| r.extensions.as_deref());
let configured_extensions: Vec<ExtensionConfig> = if session_config.resume {
EnabledExtensionsState::for_session(
&agent.config.session_manager,
@ -418,7 +428,7 @@ async fn collect_extension_configs(
} else if session_config.no_profile {
Vec::new()
} else {
resolve_extensions_for_new_session(recipe.and_then(|r| r.extensions.as_deref()), None)
resolve_extensions_for_new_session(recipe_extensions, None)
};
let cli_flag_extensions = parse_cli_flag_extensions(
@ -428,6 +438,12 @@ async fn collect_extension_configs(
);
let mut all: Vec<ExtensionConfig> = configured_extensions;
if !session_config.no_profile && !session_config.resume && recipe_extensions.is_none() {
let project_root = std::env::current_dir().ok();
all.extend(goose::plugins::mcp_servers::enabled_plugin_mcp_servers(
project_root.as_deref(),
));
}
all.extend(cli_flag_extensions.into_iter().map(|(_, cfg)| cfg));
Ok(all)
@ -531,29 +547,79 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
}
};
let new_provider = match create(
&resolved.provider_name,
resolved.model_config,
extensions_for_provider.clone(),
)
.await
{
Ok(provider) => provider,
Err(e) => {
output::render_error(&format!(
let (new_provider, effective_provider_name, effective_model_name, effective_model_config) =
match create(&resolved.provider_name, extensions_for_provider.clone()).await {
Ok(provider) => (
provider,
resolved.provider_name.clone(),
resolved.model_name.clone(),
resolved.model_config.clone(),
),
Err(e)
if session_config.resume
&& session_config.provider.is_none()
&& is_provider_unavailable_error(&e) =>
{
let fallback_provider = config.get_goose_provider().unwrap_or_else(|_| {
output::render_error("No provider configured. Run 'goose configure' first.");
process::exit(1);
});
let fallback_model = config.get_goose_model().unwrap_or_else(|_| {
output::render_error("No model configured. Run 'goose configure' first.");
process::exit(1);
});
eprintln!(
"{}",
style(format!(
"Warning: Could not create the session's original provider '{}' ({}). \
Falling back to the default provider '{}'.",
resolved.provider_name, e, fallback_provider
))
.yellow()
);
let fallback_model_config =
model_config_from_user_config(fallback_provider.as_str(), &fallback_model)
.unwrap_or_else(|e| {
output::render_error(&format!(
"Failed to create model configuration: {}",
e
));
process::exit(1);
});
match create(&fallback_provider, extensions_for_provider.clone()).await {
Ok(provider) => (
provider,
fallback_provider,
fallback_model,
fallback_model_config,
),
Err(e2) => {
output::render_error(&format!(
"Error {}.\n\
Please check your system keychain and run 'goose configure' again.\n\
If your system is unable to use the keyring, please try setting secret key(s) via environment variables.\n\
For more info, see: https://goose-docs.ai/docs/troubleshooting/#keychainkeyring-errors",
e2
));
process::exit(1);
}
}
}
Err(e) => {
output::render_error(&format!(
"Error {}.\n\
Please check your system keychain and run 'goose configure' again.\n\
If your system is unable to use the keyring, please try setting secret key(s) via environment variables.\n\
For more info, see: https://goose-docs.ai/docs/troubleshooting/#keychainkeyring-errors",
e
));
process::exit(1);
}
};
tracing::info!("🤖 Using model: {}", resolved.model_name);
process::exit(1);
}
};
tracing::info!("🤖 Using model: {}", effective_model_name);
agent
.update_provider(new_provider, &session_id)
.update_provider(new_provider, effective_model_config, &session_id)
.await
.unwrap_or_else(|e| {
output::render_error(&format!("Failed to initialize agent: {}", e));
@ -605,6 +671,7 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
edit_mode,
recipe.and_then(|r| r.retry.clone()),
session_config.output_format.clone(),
session_config.stats,
)
.await;
@ -613,17 +680,26 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
if !session_config.quiet {
output::display_session_info(
session_config.resume,
&resolved.provider_name,
&resolved.model_name,
&effective_provider_name,
&effective_model_name,
&Some(session_id),
);
}
session
}
fn is_provider_unavailable_error(e: &anyhow::Error) -> bool {
let msg = e.to_string();
msg.contains("is not set")
|| msg.contains("not configured")
|| msg.contains("Configuration value not found")
}
#[cfg(test)]
mod tests {
use super::*;
use goose::session::SessionManager;
use tempfile::TempDir;
#[test]
fn test_session_builder_config_creation() {
@ -651,6 +727,7 @@ mod tests {
quiet: false,
output_format: "text".to_string(),
container: None,
stats: false,
};
assert_eq!(config.extensions.len(), 1);
@ -686,6 +763,44 @@ mod tests {
assert!(!config.fork);
}
#[tokio::test]
async fn test_implicit_resume_ignores_newer_scheduled_sessions() {
let temp_dir = TempDir::new().unwrap();
let session_manager = SessionManager::new(temp_dir.path().to_path_buf());
let goose_mode = GooseMode::default();
let user_session = session_manager
.create_session(
temp_dir.path().to_path_buf(),
"User session".to_string(),
SessionType::User,
goose_mode,
)
.await
.unwrap();
session_manager
.create_session(
temp_dir.path().to_path_buf(),
"Scheduled job: test".to_string(),
SessionType::Scheduled,
goose_mode,
)
.await
.unwrap();
let resolved = resolve_session_id(
&SessionBuilderConfig {
resume: true,
..SessionBuilderConfig::default()
},
&session_manager,
goose_mode,
)
.await;
assert_eq!(resolved, user_session.id);
}
#[test]
fn test_truncate_with_ellipsis() {
assert_eq!(truncate_with_ellipsis("abc", 5), "abc");

View file

@ -1,3 +1,4 @@
use goose::agents::execute_commands::list_commands;
use goose::config::GooseMode;
use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::highlight::{CmdKind, Highlighter};
@ -36,8 +37,8 @@ impl GooseCompleter {
// Create completion candidates that match the prefix
let candidates: Vec<Pair> = cache
.prompts
.iter()
.flat_map(|(_, names)| names)
.values()
.flatten()
.filter(|name| name.starts_with(prefix.trim()))
.map(|name| Pair {
display: name.clone(),
@ -129,7 +130,6 @@ impl GooseCompleter {
let skills = list_installed_skills(Some(&cwd));
let skill_names: Vec<String> = skills.iter().map(|s| s.name.clone()).collect();
// Complete the last letter being typed (e.g. "/skills coding in<tab>")
let last = line.rsplit_once(' ').map_or("", |(_, w)| w);
let pos = line.len() - last.len();
@ -146,23 +146,32 @@ impl GooseCompleter {
Ok((pos, candidates))
}
/// Complete model names for the /model command.
fn complete_model_names(&self, line: &str) -> Result<(usize, Vec<Pair>)> {
Ok((line.len(), vec![]))
}
/// Complete slash commands
fn complete_slash_commands(&self, line: &str) -> Result<(usize, Vec<Pair>)> {
// Define available slash commands
let commands = [
"/exit",
"/quit",
"/help",
"/?",
"/t",
"/extension",
"/builtin",
"/prompts",
"/prompt",
"/mode",
"/recipe",
"/skills",
let mut commands = vec![
"/exit".to_string(),
"/quit".to_string(),
"/help".to_string(),
"/?".to_string(),
"/t".to_string(),
"/extension".to_string(),
"/builtin".to_string(),
"/mode".to_string(),
"/model".to_string(),
"/recipe".to_string(),
];
commands.extend(
list_commands()
.iter()
.map(|command| format!("/{}", command.name)),
);
commands.sort();
commands.dedup();
// Find commands that match the prefix
let matching_commands: Vec<Pair> = commands
@ -396,6 +405,10 @@ impl Completer for GooseCompleter {
}
}
if line.starts_with("/model") {
return self.complete_model_names(line);
}
if line.starts_with("/mode") {
return self.complete_mode_flags(line);
}
@ -568,12 +581,35 @@ mod tests {
let (pos, candidates) = completer.complete_slash_commands("/").unwrap();
assert_eq!(pos, 0);
assert!(candidates.len() > 1);
for command in list_commands() {
assert!(
candidates
.iter()
.any(|candidate| candidate.display == format!("/{}", command.name)),
"slash completion should list /{}",
command.name
);
}
// Test no match
let (_pos, candidates) = completer.complete_slash_commands("/nonexistent").unwrap();
assert_eq!(candidates.len(), 0);
}
#[test]
fn test_complete_model_names() {
let cache = create_test_cache();
let completer = GooseCompleter::new(cache);
let (pos, candidates) = completer.complete_model_names("/model ").unwrap();
assert_eq!(pos, "/model ".len());
assert!(candidates.is_empty());
let (pos, candidates) = completer.complete_model_names("/model gpt").unwrap();
assert_eq!(pos, "/model gpt".len());
assert!(candidates.is_empty());
}
#[test]
fn test_complete_prompt_names() {
let cache = create_test_cache();

View file

@ -1,7 +1,10 @@
use anyhow::Result;
use anyhow::{Context, Result};
use goose::config::Config;
use goose::conversation::message::Message;
use goose::conversation::Conversation;
use std::fs;
use std::io::Read;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use tempfile::Builder;
@ -21,9 +24,6 @@ pub fn resolve_editor_command() -> Option<String> {
)
}
/// Inner resolution logic, separated for testability.
/// Checks sources in priority order: config, VISUAL, EDITOR.
/// Skips empty strings at each level.
fn resolve_editor_from_sources(
config_editor: Option<&str>,
visual: Option<&str>,
@ -37,6 +37,57 @@ fn resolve_editor_from_sources(
None
}
/// Resolve the editor command, falling back to vi (or notepad on Windows).
pub fn resolve_editor_or_default() -> String {
let config = Config::global();
let config_editor = config.get_goose_prompt_editor().ok().flatten();
let visual = std::env::var("VISUAL").ok();
let editor_env = std::env::var("EDITOR").ok();
resolve_editor_or_default_from_sources(
config_editor.as_deref(),
visual.as_deref(),
editor_env.as_deref(),
)
}
fn resolve_editor_default() -> String {
if cfg!(windows) {
"notepad".to_string()
} else {
"vi".to_string()
}
}
fn resolve_editor_or_default_from_sources(
config_editor: Option<&str>,
visual: Option<&str>,
editor_env: Option<&str>,
) -> String {
resolve_editor_from_sources(config_editor, visual, editor_env)
.unwrap_or_else(resolve_editor_default)
}
/// Open a YAML temp file with the user's editor to edit a conversation.
/// Returns the edited conversation, or an error if the editor failed or YAML was invalid.
pub fn edit_conversation(conversation: &Conversation) -> Result<Conversation> {
let yaml = serde_yaml::to_string(conversation.messages())?;
let mut tmp = NamedTempFile::with_suffix(".yaml")?;
tmp.write_all(yaml.as_bytes())?;
tmp.flush()?;
let editor = resolve_editor_or_default();
let path = tmp.path().to_path_buf();
launch_editor(&editor, &path).with_context(|| format!("failed to launch editor '{editor}'"))?;
let edited = std::fs::read_to_string(&path)?;
let messages: Vec<Message> =
serde_yaml::from_str(&edited).context("invalid YAML — session unchanged")?;
Ok(Conversation::new_unvalidated(messages))
}
/// Build the markdown template content for the editor prompt.
fn build_template(messages: &[&str], prefill: Option<&str>) -> String {
let mut content = String::from("# Goose Prompt Editor\n\n");
@ -84,21 +135,36 @@ impl SymlinkCleanup {
impl Drop for SymlinkCleanup {
fn drop(&mut self) {
// Always try to clean up the symlink, ignoring any errors
let _ = std::fs::remove_file(&self.symlink_path);
}
}
/// Split an editor command into program and arguments.
///
/// Uses shell-word splitting only when the command contains quotes, so values like
/// `"/Applications/Sublime Text.app/.../subl" -w` work. Unquoted commands are split on
/// whitespace to avoid shlex stripping backslashes from Windows paths like
/// `C:\Windows\System32\notepad.exe`.
fn split_editor_command(editor_cmd: &str) -> Result<Vec<String>> {
if editor_cmd.contains(['"', '\'']) {
shlex::split(editor_cmd).ok_or_else(|| {
anyhow::anyhow!("Invalid editor command: unmatched quotes in '{editor_cmd}'")
})
} else {
Ok(editor_cmd.split_whitespace().map(String::from).collect())
}
}
/// Launch editor and wait for completion
fn launch_editor(editor_cmd: &str, file_path: &PathBuf) -> Result<()> {
use std::process::Stdio;
let parts: Vec<&str> = editor_cmd.split_whitespace().collect();
let parts = split_editor_command(editor_cmd)?;
if parts.is_empty() {
return Err(anyhow::anyhow!("Empty editor command"));
}
let mut cmd = Command::new(parts[0]);
let mut cmd = Command::new(&parts[0]);
if let Ok(cwd) = std::env::current_dir() {
cmd.current_dir(cwd);
}
@ -414,54 +480,64 @@ with multiple lines.
);
}
// --- resolve_editor_from_sources tests ---
#[test]
fn test_resolve_editor_returns_config_when_set() {
let result = resolve_editor_from_sources(Some("code"), Some("vim"), Some("nano"));
assert_eq!(result.as_deref(), Some("code"));
fn test_resolve_editor_resolution_priority() {
assert_eq!(
resolve_editor_from_sources(Some("config-val"), Some("visual-val"), Some("editor-val")),
Some("config-val".to_string())
);
assert_eq!(
resolve_editor_from_sources(Some(""), Some("visual-val"), Some("editor-val")),
Some("visual-val".to_string())
);
assert_eq!(
resolve_editor_from_sources(None, Some(""), Some("editor-val")),
Some("editor-val".to_string())
);
assert_eq!(resolve_editor_from_sources(None, None, None), None);
assert_eq!(
resolve_editor_from_sources(Some(""), Some(""), Some("")),
None
);
let default_val = resolve_editor_default();
assert_eq!(
resolve_editor_or_default_from_sources(None, None, None),
default_val
);
assert_eq!(
resolve_editor_or_default_from_sources(Some(""), Some(""), Some("")),
default_val
);
}
#[test]
fn test_resolve_editor_falls_back_to_visual() {
let result = resolve_editor_from_sources(None, Some("vim"), Some("nano"));
assert_eq!(result.as_deref(), Some("vim"));
}
fn test_split_editor_command() {
assert_eq!(
split_editor_command("code --wait").unwrap(),
vec!["code", "--wait"]
);
#[test]
fn test_resolve_editor_falls_back_to_editor_env() {
let result = resolve_editor_from_sources(None, None, Some("nano"));
assert_eq!(result.as_deref(), Some("nano"));
}
assert_eq!(
split_editor_command(
r#""/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl" -w"#
)
.unwrap(),
vec![
"/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl",
"-w"
]
);
#[test]
fn test_resolve_editor_returns_none_when_nothing_set() {
let result = resolve_editor_from_sources(None, None, None);
assert_eq!(result, None);
}
assert_eq!(
split_editor_command(r"C:\Windows\System32\notepad.exe").unwrap(),
vec![r"C:\Windows\System32\notepad.exe"]
);
#[test]
fn test_resolve_editor_skips_empty_config() {
let result = resolve_editor_from_sources(Some(""), Some("vim"), None);
assert_eq!(result.as_deref(), Some("vim"));
}
#[test]
fn test_resolve_editor_skips_empty_visual() {
let result = resolve_editor_from_sources(None, Some(""), Some("nano"));
assert_eq!(result.as_deref(), Some("nano"));
}
#[test]
fn test_resolve_editor_skips_all_empty() {
let result = resolve_editor_from_sources(Some(""), Some(""), Some(""));
assert_eq!(result, None);
}
#[test]
fn test_resolve_editor_skips_empty_config_and_visual() {
let result = resolve_editor_from_sources(Some(""), Some(""), Some("emacs"));
assert_eq!(result.as_deref(), Some("emacs"));
assert!(split_editor_command(r#"code --wait "unclosed"#).is_err());
}
// --- build_template edge case tests ---
@ -469,9 +545,7 @@ with multiple lines.
#[test]
fn test_build_template_empty_prefill_string() {
let content = build_template(&["## User: Hello"], Some(""));
// Empty prefill should not appear in content
assert!(content.contains("# Your prompt:\n\n#"));
// Should go directly to conversation context
assert!(content.contains("# Recent conversation for context"));
}
@ -498,11 +572,8 @@ with multiple lines.
assert!(prefill_pos < context_pos);
}
// --- extract_user_input with prefilled content tests ---
#[test]
fn test_extract_user_input_with_prefill_kept() {
// Simulates a user who opened the editor with prefill and kept it unchanged
let content = build_template(&["## User: Hello"], Some("fix the login bug"));
let result = extract_user_input(&content);
assert_eq!(result, "fix the login bug");
@ -510,7 +581,6 @@ with multiple lines.
#[test]
fn test_extract_user_input_with_prefill_edited() {
// Simulates a user who edited the prefill text
let mut content = build_template(&["## User: Hello"], Some("fix the login bug"));
content = content.replace(
"fix the login bug",
@ -522,7 +592,6 @@ with multiple lines.
#[test]
fn test_extract_user_input_prefill_replaced() {
// Simulates a user who deleted the prefill and wrote something new
let mut content = build_template(&["## User: Hello"], Some("fix the login bug"));
content = content.replace("fix the login bug\n", "completely different prompt\n");
let result = extract_user_input(&content);
@ -531,7 +600,6 @@ with multiple lines.
#[test]
fn test_extract_user_input_prefill_cleared() {
// Simulates a user who deleted the prefill and left nothing
let mut content = build_template(&["## User: Hello"], Some("fix the login bug"));
content = content.replace("fix the login bug\n", "");
let result = extract_user_input(&content);

View file

@ -1,12 +1,15 @@
use console::style;
use rmcp::model::ElicitationAction;
use serde_json::Value;
use std::collections::HashMap;
use std::io::{self, BufRead, IsTerminal, Write};
pub fn collect_elicitation_input(
message: &str,
schema: &Value,
) -> io::Result<Option<HashMap<String, Value>>> {
pub struct ElicitationInput {
pub action: ElicitationAction,
pub user_data: HashMap<String, Value>,
}
pub fn collect_elicitation_input(message: &str, schema: &Value) -> io::Result<ElicitationInput> {
if !message.is_empty() {
println!("\n{}", style(message).cyan());
}
@ -24,9 +27,18 @@ pub fn collect_elicitation_input(
"Approve?"
};
return match cliclack::confirm(prompt).initial_value(true).interact() {
Ok(true) => Ok(Some(HashMap::new())),
Ok(false) => Ok(None),
Err(e) if e.kind() == io::ErrorKind::Interrupted => Ok(None),
Ok(true) => Ok(ElicitationInput {
action: ElicitationAction::Accept,
user_data: HashMap::new(),
}),
Ok(false) => Ok(ElicitationInput {
action: ElicitationAction::Decline,
user_data: HashMap::new(),
}),
Err(e) if e.kind() == io::ErrorKind::Interrupted => Ok(ElicitationInput {
action: ElicitationAction::Cancel,
user_data: HashMap::new(),
}),
Err(e) => Err(e),
};
}
@ -65,7 +77,12 @@ pub fn collect_elicitation_input(
Ok(v) => {
data.insert(name.clone(), Value::Bool(v));
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => return Ok(None),
Err(e) if e.kind() == io::ErrorKind::Interrupted => {
return Ok(ElicitationInput {
action: ElicitationAction::Cancel,
user_data: HashMap::new(),
});
}
Err(e) => return Err(e),
}
continue;
@ -93,7 +110,10 @@ pub fn collect_elicitation_input(
// Handle Ctrl+C / EOF for cancellation
if input.is_none() {
return Ok(None);
return Ok(ElicitationInput {
action: ElicitationAction::Cancel,
user_data: HashMap::new(),
});
}
let input = input.unwrap();
@ -114,12 +134,18 @@ pub fn collect_elicitation_input(
"{}",
style(format!("Required field '{}' is missing", name)).red()
);
return Ok(None);
return Ok(ElicitationInput {
action: ElicitationAction::Decline,
user_data: HashMap::new(),
});
}
}
println!();
Ok(Some(data))
Ok(ElicitationInput {
action: ElicitationAction::Accept,
user_data: data,
})
}
fn read_line() -> io::Result<Option<String>> {

View file

@ -353,7 +353,7 @@ pub fn message_to_markdown(message: &Message, export_all_content: bool) -> Strin
message
));
}
ActionRequiredData::ElicitationResponse { id, user_data } => {
ActionRequiredData::ElicitationResponse { id, user_data, .. } => {
md.push_str(&format!(
"**Action Required** (elicitation_response): {}\n```json\n{}\n```\n\n",
id,

View file

@ -20,6 +20,7 @@ pub enum InputResult {
ListPrompts(Option<String>),
PromptCommand(PromptCommandOptions),
GooseMode(String),
Model(Option<String>),
Plan(PlanCommandOptions),
EndPlan,
Clear,
@ -196,6 +197,8 @@ fn handle_slash_command(input: &str) -> Option<InputResult> {
const CMD_EXTENSION: &str = "/extension ";
const CMD_BUILTIN: &str = "/builtin ";
const CMD_MODE: &str = "/mode ";
const CMD_MODEL: &str = "/model";
const CMD_MODEL_WITH_SPACE: &str = "/model ";
const CMD_PLAN: &str = "/plan";
const CMD_ENDPLAN: &str = "/endplan";
const CMD_CLEAR: &str = "/clear";
@ -261,6 +264,19 @@ fn handle_slash_command(input: &str) -> Option<InputResult> {
s if s.starts_with(CMD_MODE) => Some(InputResult::GooseMode(
s.get(CMD_MODE.len()..).unwrap_or("").to_string(),
)),
s if s == CMD_MODEL => Some(InputResult::Model(None)),
s if s.starts_with(CMD_MODEL_WITH_SPACE) => {
let model = s
.get(CMD_MODEL_WITH_SPACE.len()..)
.unwrap_or("")
.trim()
.to_string();
if model.is_empty() {
Some(InputResult::Model(None))
} else {
Some(InputResult::Model(Some(model)))
}
}
s if s.starts_with(CMD_PLAN) => {
parse_plan_command(s.get(CMD_PLAN.len()..).unwrap_or("").trim().to_string())
}
@ -385,10 +401,17 @@ fn parse_plan_command(input: String) -> Option<InputResult> {
Some(InputResult::Plan(options))
}
fn print_help() {
fn help_text() -> String {
let newline_key = get_newline_key().to_ascii_uppercase();
let modes = GooseMode::VARIANTS.join(", ");
println!(
let additional_builtin_help = additional_builtin_help();
let additional_builtin_help = if additional_builtin_help.is_empty() {
String::new()
} else {
format!("{additional_builtin_help}\n")
};
format!(
"Available commands:
/exit or /quit - Exit the session
/t - Toggle Light/Dark/Ansi theme
@ -399,6 +422,7 @@ fn print_help() {
/prompts [--extension <name>] - List all available prompts, optionally filtered by extension
/prompt <n> [--info] [key=value...] - Get prompt info or execute a prompt
/mode <name> - Set the goose mode to use ({modes})
/model [name] - Show the current model, or switch models for this session while keeping the same provider
/plan <message_text> - Enters 'plan' mode with optional message. Create a plan based on the current messages and asks user if they want to act on it.
If user acts on the plan, goose mode is set to 'auto' and returns to 'normal' goose mode.
To warm up goose before using '/plan', we recommend setting '/mode approve' & putting appropriate context into goose.
@ -408,6 +432,7 @@ fn print_help() {
/recipe [filepath] - Generate a recipe from the current conversation and save it to the specified filepath (must end with .yaml).
If no filepath is provided, it will be saved to ./recipe.yaml.
/compact - Compact the current conversation to reduce context length while preserving key information.
{additional_builtin_help}/status - Show session status: model, provider, mode, and token usage.
/edit [text] - Open your prompt editor to compose a message. Optionally pre-fill with text.
Uses $GOOSE_PROMPT_EDITOR, $VISUAL, or $EDITOR (in that order).
/skills - List available skills or enable skills by name (usage: /skills [<name>...])
@ -418,7 +443,23 @@ Navigation:
Ctrl+C - Clear current line if text is entered, otherwise exit the session
Ctrl+{newline_key} - Add a newline (configurable via GOOSE_CLI_NEWLINE_KEY)
Up/Down arrows - Navigate through command history"
);
)
}
fn additional_builtin_help() -> String {
const DOCUMENTED_BUILTINS: &[&str] =
&["prompts", "prompt", "compact", "clear", "skills", "status"];
goose::agents::execute_commands::list_commands()
.iter()
.filter(|command| !DOCUMENTED_BUILTINS.contains(&command.name))
.map(|command| format!("/{} - {}", command.name, command.description))
.collect::<Vec<_>>()
.join("\n")
}
fn print_help() {
println!("{}", help_text());
}
/// Extract recent messages for editor context
@ -498,10 +539,38 @@ mod tests {
panic!("Expected AddBuiltin");
}
// Test model command
assert!(matches!(
handle_slash_command("/model"),
Some(InputResult::Model(None))
));
assert!(matches!(
handle_slash_command("/model "),
Some(InputResult::Model(None))
));
if let Some(InputResult::Model(Some(model))) = handle_slash_command("/model gpt-4.1") {
assert_eq!(model, "gpt-4.1");
} else {
panic!("Expected Model");
}
// Test unknown commands
assert!(handle_slash_command("/unknown").is_none());
}
#[test]
fn help_lists_builtin_agent_commands() {
let help = help_text();
for command in goose::agents::execute_commands::list_commands() {
assert!(
help.contains(&format!("/{}", command.name)),
"help output should list /{}",
command.name
);
}
}
#[test]
fn test_prompts_command() {
// Test basic prompts command

View file

@ -1,6 +1,6 @@
mod builder;
mod completion;
mod editor;
pub mod editor;
mod elicitation;
mod export;
mod input;
@ -13,6 +13,7 @@ use crate::session::task_execution_display::{
format_task_execution_notification, TASK_EXECUTION_NOTIFICATION_TYPE,
};
use goose::conversation::Conversation;
use std::env;
use std::io::Write;
use std::str::FromStr;
use tokio::signal::ctrl_c;
@ -27,6 +28,7 @@ use goose::permission::permission_confirmation::PrincipalType;
use goose::permission::Permission;
use goose::permission::PermissionConfirmation;
use goose::providers::base::Provider;
use goose::providers::base::ProviderUsage;
use goose::utils::safe_truncate;
use anyhow::{Context, Result};
@ -37,8 +39,8 @@ use goose::agents::{Agent, SessionConfig, COMPACT_TRIGGERS};
use goose::config::extensions::name_to_key;
use goose::config::{Config, GooseMode};
use input::InputResult;
use rmcp::model::PromptMessage;
use rmcp::model::ServerNotification;
use rmcp::model::{ElicitationAction, PromptMessage};
use rmcp::model::{ErrorCode, ErrorData};
use strum::VariantNames;
@ -51,11 +53,13 @@ use std::collections::{HashMap, HashSet};
use std::io::IsTerminal;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Instant;
use std::time::{Duration, Instant};
use tokio;
use tokio_util::sync::CancellationToken;
use tracing::warn;
const GOOSE_PLANNER_CONTEXT_LIMIT: &str = "GOOSE_PLANNER_CONTEXT_LIMIT";
#[derive(Serialize, Deserialize, Debug)]
struct JsonOutput {
messages: Vec<Message>,
@ -172,6 +176,7 @@ pub struct CliSession {
edit_mode: Option<EditMode>,
retry_config: Option<RetryConfig>,
output_format: String,
stats: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -214,22 +219,23 @@ pub async fn classify_planner_response(
session_id: &str,
message_text: String,
provider: Arc<dyn Provider>,
model_config: goose_providers::model::ModelConfig,
) -> Result<PlannerResponseType> {
let prompt = format!(
"The text below is the output from an AI model which can either provide a plan or list of clarifying questions. Based on the text below, decide if the output is a \"plan\" or \"clarifying questions\".\n---\n{message_text}"
);
let message = Message::user().with_text(&prompt);
let model_config = provider.get_model_config();
let (result, _usage) = provider
.complete(
let (result, _usage) = goose::session_context::with_session_id(
Some(session_id.to_string()),
provider.complete(
&model_config,
session_id,
"Reply only with the classification label: \"plan\" or \"clarifying questions\"",
&[message],
&[],
)
.await?;
),
)
.await?;
let predicted = result.as_concat_text();
if predicted.to_lowercase().contains("plan") {
@ -250,6 +256,7 @@ impl CliSession {
edit_mode: Option<EditMode>,
retry_config: Option<RetryConfig>,
output_format: String,
stats: bool,
) -> Self {
let messages = agent
.config
@ -271,6 +278,7 @@ impl CliSession {
edit_mode,
retry_config,
output_format,
stats,
}
}
@ -312,6 +320,7 @@ impl CliSession {
env_keys: Vec::new(),
description: goose::config::DEFAULT_EXTENSION_DESCRIPTION.to_string(),
timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT),
cwd: None,
bundled: None,
available_tools: Vec::new(),
})
@ -478,10 +487,6 @@ impl CliSession {
/// Start an interactive session, optionally with an initial message
pub async fn interactive(&mut self, prompt: Option<String>) -> Result<()> {
self.agent
.emit_hook(goose::hooks::HookEvent::SessionStart, &self.session_id)
.await;
let result = self.run_interactive(prompt).await;
self.agent
@ -608,6 +613,10 @@ impl CliSession {
history.save(editor);
self.handle_goose_mode(&mode).await?;
}
InputResult::Model(model) => {
history.save(editor);
self.handle_model(model.as_deref()).await?;
}
InputResult::Plan(options) => {
self.handle_plan_mode(options).await?;
}
@ -643,6 +652,8 @@ impl CliSession {
prefill.as_deref(),
) {
Ok((message, true)) => {
editor.add_history_entry(message.as_str())?;
history.save(editor);
self.handle_message_input(&message, history, editor).await?;
}
Ok((_, false)) => {}
@ -710,8 +721,8 @@ impl CliSession {
RunMode::Plan => {
let mut plan_messages = self.messages.clone();
plan_messages.push(Message::user().with_text(content));
let reasoner = get_reasoner().await?;
self.plan_with_reasoner_model(plan_messages, reasoner)
let (reasoner, reasoner_model_config) = get_reasoner().await?;
self.plan_with_reasoner_model(plan_messages, reasoner, reasoner_model_config)
.await?;
}
}
@ -795,6 +806,78 @@ impl CliSession {
Ok(())
}
async fn handle_model(&self, model: Option<&str>) -> Result<()> {
let provider = self.agent.provider().await?;
let current_provider_name = provider.get_name().to_string();
let current_model_config = self
.agent
.model_config_for_session(&self.session_id)
.await?;
let current_model_name = current_model_config.model_name.clone();
if model.is_none() {
output::goose_mode_message(&format!(
"Current session model: '{}' (provider '{}')",
current_model_name, current_provider_name
));
return Ok(());
}
let model_name = model.unwrap_or_default().trim();
if model_name.is_empty() {
output::render_error("Model name cannot be empty");
return Ok(());
}
if current_provider_name.ends_with("-acp") {
output::render_error(
"Session model switching is not supported for ACP providers in the CLI.",
);
return Ok(());
}
if provider.manages_own_context() {
output::render_error(&format!(
"Session model switching is not supported for provider '{}' because it manages its own conversation context.",
current_provider_name
));
return Ok(());
}
let new_model_config =
build_switched_model_config(&current_provider_name, model_name, &current_model_config)?;
let configured_effort = Config::global().get_goose_thinking_effort();
let new_effort = new_model_config.thinking_effort().or(configured_effort);
let current_effort = current_model_config.thinking_effort().or(configured_effort);
if new_model_config.model_name == current_model_config.model_name
&& new_effort == current_effort
{
output::goose_mode_message(&format!(
"Session already using model '{}' for provider '{}'",
current_model_name, current_provider_name
));
return Ok(());
}
let extensions = self.agent.get_extension_configs().await;
let new_provider = goose::providers::create(&current_provider_name, extensions)
.await
.map_err(|e| anyhow::anyhow!("Failed to create provider: {e}"))?;
self.agent
.update_provider(new_provider, new_model_config, &self.session_id)
.await?;
let mode = self.agent.goose_mode().await;
self.agent.update_goose_mode(mode, &self.session_id).await?;
output::goose_mode_message(&format!(
"Session model switched from '{}' to '{}' for provider '{}'",
current_model_name, model_name, current_provider_name
));
Ok(())
}
async fn handle_plan_mode(&mut self, options: input::PlanCommandOptions) -> Result<()> {
self.run_mode = RunMode::Plan;
output::render_enter_plan_mode();
@ -806,8 +889,9 @@ impl CliSession {
let mut plan_messages = self.messages.clone();
plan_messages.push(Message::user().with_text(&options.message_text));
let reasoner = get_reasoner().await?;
self.plan_with_reasoner_model(plan_messages, reasoner).await
let (reasoner, reasoner_model_config) = get_reasoner().await?;
self.plan_with_reasoner_model(plan_messages, reasoner, reasoner_model_config)
.await
}
async fn handle_clear(&mut self) -> Result<()> {
@ -827,9 +911,11 @@ impl CliSession {
.config
.session_manager
.update(&self.session_id)
.total_tokens(Some(0))
.input_tokens(Some(0))
.output_tokens(Some(0))
.usage(goose_providers::conversation::token_usage::Usage::new(
Some(0),
Some(0),
Some(0),
))
.apply()
.await
{
@ -967,25 +1053,24 @@ impl CliSession {
&mut self,
plan_messages: Conversation,
reasoner: Arc<dyn Provider>,
model_config: goose_providers::model::ModelConfig,
) -> Result<(), anyhow::Error> {
let plan_prompt = self.agent.get_plan_prompt(&self.session_id).await?;
output::show_thinking();
let model_config = reasoner.get_model_config();
let (plan_response, _usage) = reasoner
.complete(
&model_config,
&self.session_id,
&plan_prompt,
plan_messages.messages(),
&[],
)
.await?;
let (plan_response, _usage) = goose::session_context::with_session_id(
Some(self.session_id.clone()),
reasoner.complete(&model_config, &plan_prompt, plan_messages.messages(), &[]),
)
.await?;
output::render_message(&plan_response, self.debug);
output::hide_thinking();
let planner_response_type = classify_planner_response(
&self.session_id,
plan_response.as_concat_text(),
self.agent.provider().await?,
self.agent
.model_config_for_session(&self.session_id)
.await?,
)
.await?;
@ -1050,9 +1135,6 @@ impl CliSession {
/// Process a single message and exit
pub async fn headless(&mut self, prompt: String) -> Result<()> {
self.agent
.emit_hook(goose::hooks::HookEvent::SessionStart, &self.session_id)
.await;
let message = Message::user().with_text(&prompt);
let result = self
.process_message(message, CancellationToken::default(), false)
@ -1105,6 +1187,9 @@ impl CliSession {
let mut markdown_buffer = streaming_buffer::MarkdownBuffer::new();
let mut prompted_credits_urls: HashSet<String> = HashSet::new();
let mut thinking_header_shown = false;
let run_started = Instant::now();
let mut first_token_at: Option<Instant> = None;
let mut last_usage: Option<ProviderUsage> = None;
use futures::StreamExt;
loop {
@ -1112,6 +1197,9 @@ impl CliSession {
result = stream.next() => {
match result {
Some(Ok(AgentEvent::Message(message))) => {
if first_token_at.is_none() && message_has_text(&message) {
first_token_at = Some(Instant::now());
}
if let Some((id, security_prompt)) = find_tool_confirmation(&message) {
let permission = if interactive {
prompt_tool_confirmation(&security_prompt)?
@ -1177,25 +1265,37 @@ impl CliSession {
let _ = progress_bars.hide();
match elicitation::collect_elicitation_input(&elicitation_message, &schema) {
Ok(Some(user_data)) => {
let user_data_value = serde_json::to_value(user_data)
Ok(input) => {
match &input.action {
ElicitationAction::Decline => {
output::render_text("Information request declined.", Some(Color::Yellow), true);
}
ElicitationAction::Cancel => {
output::render_text("Information request cancelled.", Some(Color::Yellow), true);
}
ElicitationAction::Accept => {}
}
let should_cancel = input.action == ElicitationAction::Cancel;
let action = input.action;
let user_data_value = serde_json::to_value(input.user_data)
.unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
let response_message = Message::user()
.with_content(MessageContent::action_required_elicitation_response(
elicitation_id,
user_data_value,
action,
))
.with_visibility(false, true);
self.messages.push(response_message.clone());
// Elicitation responses return an empty stream - the response
// unblocks the waiting tool call via ActionRequiredManager
let _ = self.agent.reply(response_message, session_config.clone(), Some(cancel_token.clone())).await?;
}
Ok(None) => {
output::render_text("Information request cancelled.", Some(Color::Yellow), true);
cancel_token_clone.cancel();
drop(stream);
break;
if should_cancel {
cancel_token_clone.cancel();
drop(stream);
break;
}
}
Err(e) => {
output::render_error(&format!("Failed to collect input: {}", e));
@ -1223,6 +1323,10 @@ impl CliSession {
}
}
}
Some(Ok(AgentEvent::Usage(usage))) => {
last_usage = Some(usage);
}
Some(Ok(AgentEvent::MessageUsage { .. })) => {}
Some(Ok(AgentEvent::McpNotification((extension_id, notification)))) => {
handle_mcp_notification(
&extension_id,
@ -1279,9 +1383,18 @@ impl CliSession {
.await
{
Ok(session) => JsonMetadata {
total_tokens: session.accumulated_total_tokens.or(session.total_tokens),
input_tokens: session.accumulated_input_tokens.or(session.input_tokens),
output_tokens: session.accumulated_output_tokens.or(session.output_tokens),
total_tokens: session
.accumulated_usage
.total_tokens
.or(session.usage.total_tokens),
input_tokens: session
.accumulated_usage
.input_tokens
.or(session.usage.input_tokens),
output_tokens: session
.accumulated_usage
.output_tokens
.or(session.usage.output_tokens),
status: "completed".to_string(),
},
Err(_) => JsonMetadata {
@ -1306,9 +1419,9 @@ impl CliSession {
.ok();
let (total_tokens, input_tokens, output_tokens) = match session {
Some(s) => (
s.accumulated_total_tokens.or(s.total_tokens),
s.accumulated_input_tokens.or(s.input_tokens),
s.accumulated_output_tokens.or(s.output_tokens),
s.accumulated_usage.total_tokens.or(s.usage.total_tokens),
s.accumulated_usage.input_tokens.or(s.usage.input_tokens),
s.accumulated_usage.output_tokens.or(s.usage.output_tokens),
),
None => (None, None, None),
};
@ -1319,6 +1432,9 @@ impl CliSession {
});
} else {
println!();
if self.stats {
print_run_stats(run_started, first_token_at, last_usage.as_ref());
}
}
Ok(())
@ -1473,14 +1589,20 @@ impl CliSession {
pub async fn get_total_token_usage(&self) -> Result<Option<i32>> {
let metadata = self.get_session().await?;
Ok(metadata.accumulated_total_tokens)
Ok(metadata.accumulated_usage.total_tokens)
}
/// Display enhanced context usage with session totals
pub async fn display_context_usage(&self) -> Result<()> {
let provider = self.agent.provider().await?;
let model_config = provider.get_model_config();
let context_limit = model_config.context_limit();
let model_config = self
.agent
.model_config_for_session(&self.session_id)
.await?;
let context_limit = provider
.get_context_limit(&model_config)
.await
.unwrap_or_else(|_| model_config.context_limit());
let config = Config::global();
let show_cost = config
@ -1493,18 +1615,15 @@ impl CliSession {
match self.get_session().await {
Ok(metadata) => {
let total_tokens = metadata.total_tokens.unwrap_or(0) as usize;
let total_tokens = metadata.usage.total_tokens.unwrap_or(0) as usize;
output::display_context_usage(total_tokens, context_limit);
if show_cost {
let input_tokens = metadata.input_tokens.unwrap_or(0) as usize;
let output_tokens = metadata.output_tokens.unwrap_or(0) as usize;
output::display_cost_usage(
&provider_name,
&model_config.model_name,
input_tokens,
output_tokens,
&metadata.usage,
);
}
}
@ -1638,6 +1757,87 @@ impl CliSession {
}
}
fn message_has_text(message: &Message) -> bool {
message.content.iter().any(
|content| matches!(content, MessageContent::Text(text) if !text.text.trim().is_empty()),
)
}
fn print_run_stats(
run_started: Instant,
first_token_at: Option<Instant>,
usage: Option<&ProviderUsage>,
) {
let elapsed = run_started.elapsed();
let stats = usage.and_then(|usage| usage.stats.as_ref());
let generation_elapsed = stats
.and_then(|stats| stats.elapsed_ms)
.map(Duration::from_millis);
let output_tokens = usage
.and_then(|usage| usage.usage.output_tokens)
.and_then(|tokens| usize::try_from(tokens).ok())
.or_else(|| stats.and_then(|stats| stats.output_tokens));
let tokens_per_second = output_tokens.map(|tokens| {
let rate_elapsed = generation_elapsed.unwrap_or(elapsed);
if rate_elapsed.as_secs_f64() > 0.0 {
tokens as f64 / rate_elapsed.as_secs_f64()
} else {
0.0
}
});
let model_load_ms = stats.and_then(|stats| stats.model_load_ms);
let generation_time_to_first_token_ms = stats.and_then(|stats| stats.time_to_first_token_ms);
eprintln!("\nStats:");
if let Some(ms) = model_load_ms {
eprintln!(" Model load: {:.2}s", ms as f64 / 1000.0);
}
if model_load_ms.is_some() {
match generation_time_to_first_token_ms {
Some(ms) => eprintln!(
" Generation time to first token: {:.2}s",
ms as f64 / 1000.0
),
None => eprintln!(" Generation time to first token: unavailable"),
}
match first_token_at {
Some(first) => eprintln!(
" End-to-end time to first token: {:.2}s",
first.duration_since(run_started).as_secs_f64()
),
None => eprintln!(" End-to-end time to first token: unavailable"),
}
} else if let Some(ms) = generation_time_to_first_token_ms {
eprintln!(" Time to first token: {:.2}s", ms as f64 / 1000.0);
} else {
match first_token_at {
Some(first) => eprintln!(
" Time to first token: {:.2}s",
first.duration_since(run_started).as_secs_f64()
),
None => eprintln!(" Time to first token: unavailable"),
}
}
match tokens_per_second {
Some(rate) => eprintln!(" Tokens/sec: {:.2}", rate),
None => eprintln!(" Tokens/sec: unavailable"),
}
if let Some(tokens) = output_tokens {
eprintln!(" Output tokens: {tokens}");
}
if let Some(draft) = stats.and_then(|stats| stats.draft.as_ref()) {
eprintln!(" Draft accept rate: {:.1}%", draft.accept_rate * 100.0);
eprintln!(
" Draft tokens: {} accepted: {} target verified: {} rounds: {}",
draft.draft_tokens, draft.accepted_tokens, draft.target_tokens, draft.rounds
);
if let Some(model) = &draft.model {
eprintln!(" Draft model: {model}");
}
}
}
fn maybe_open_credits_top_up_url(
message: &Message,
interactive: bool,
@ -2020,11 +2220,11 @@ fn handle_agent_error(e: &anyhow::Error, is_stream_json_mode: bool) {
});
}
if e.downcast_ref::<goose::providers::errors::ProviderError>()
if e.downcast_ref::<goose_providers::errors::ProviderError>()
.map(|provider_error| {
matches!(
provider_error,
goose::providers::errors::ProviderError::ContextLengthExceeded(_)
goose_providers::errors::ProviderError::ContextLengthExceeded(_)
)
})
.unwrap_or(false)
@ -2044,8 +2244,8 @@ fn handle_agent_error(e: &anyhow::Error, is_stream_json_mode: bool) {
}
}
async fn get_reasoner() -> Result<Arc<dyn Provider>, anyhow::Error> {
use goose::model::ModelConfig;
async fn get_reasoner(
) -> Result<(Arc<dyn Provider>, goose_providers::model::ModelConfig), anyhow::Error> {
use goose::providers::create;
let config = Config::global();
@ -2070,12 +2270,23 @@ async fn get_reasoner() -> Result<Arc<dyn Provider>, anyhow::Error> {
.expect("No model configured. Run 'goose configure' first")
};
let model_config =
ModelConfig::new_with_context_env(model, &provider, Some("GOOSE_PLANNER_CONTEXT_LIMIT"))?;
let extensions = goose::config::extensions::get_enabled_extensions_with_config(config);
let reasoner = create(&provider, model_config, extensions).await?;
let planner_context_limit = match env::var(GOOSE_PLANNER_CONTEXT_LIMIT)
.ok()
.map(|v| v.parse::<usize>())
{
Some(Ok(n)) if n >= 4096 => Some(n),
Some(Ok(_)) => anyhow::bail!("{} must be at least 4096", GOOSE_PLANNER_CONTEXT_LIMIT),
Some(Err(e)) => anyhow::bail!("{}: {}", GOOSE_PLANNER_CONTEXT_LIMIT, e),
None => None,
};
Ok(reasoner)
let model_config =
goose::model_config::model_config_from_user_config(&provider, model.as_str())?
.with_context_limit(planner_context_limit);
let extensions = goose::config::extensions::get_enabled_extensions_with_config(config);
let reasoner = create(&provider, extensions).await?;
Ok((reasoner, model_config))
}
/// Format elapsed time duration
@ -2091,11 +2302,27 @@ fn format_elapsed_time(duration: std::time::Duration) -> String {
}
}
fn build_switched_model_config(
provider_name: &str,
model_name: &str,
current_model_config: &goose_providers::model::ModelConfig,
) -> Result<goose_providers::model::ModelConfig> {
goose::model_config::model_config_from_user_config(provider_name, model_name)
.map(|config| {
config
.with_temperature(current_model_config.temperature)
.with_toolshim(current_model_config.toolshim)
.with_toolshim_model(current_model_config.toolshim_model.clone())
})
.map_err(|e| anyhow::anyhow!("Failed to create model configuration: {e}"))
}
#[cfg(test)]
mod tests {
use super::*;
use goose::agents::extension::Envs;
use goose::config::ExtensionConfig;
use std::collections::HashMap;
use std::time::Duration;
use test_case::test_case;
@ -2175,6 +2402,7 @@ mod tests {
env_keys: vec![],
description: goose::config::DEFAULT_EXTENSION_DESCRIPTION.to_string(),
timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT),
cwd: None,
bundled: None,
available_tools: vec![],
}
@ -2190,6 +2418,7 @@ mod tests {
env_keys: vec![],
description: goose::config::DEFAULT_EXTENSION_DESCRIPTION.to_string(),
timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT),
cwd: None,
bundled: None,
available_tools: vec![],
}
@ -2205,6 +2434,7 @@ mod tests {
env_keys: vec![],
description: goose::config::DEFAULT_EXTENSION_DESCRIPTION.to_string(),
timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT),
cwd: None,
bundled: None,
available_tools: vec![],
}
@ -2219,6 +2449,73 @@ mod tests {
assert!(CliSession::parse_stdio_extension("").is_err());
}
#[test]
fn test_build_switched_model_config_rebuilds_target_model_settings() {
let _guard = env_lock::lock_env([
("GOOSE_MAX_TOKENS", None::<&str>),
("GOOSE_TEMPERATURE", None::<&str>),
("GOOSE_CONTEXT_LIMIT", None::<&str>),
("GOOSE_TOOLSHIM", None::<&str>),
("GOOSE_TOOLSHIM_OLLAMA_MODEL", None::<&str>),
]);
let current_model_config = goose_providers::model::ModelConfig {
model_name: "gpt-4o".to_string(),
context_limit: Some(128_000),
temperature: Some(0.25),
max_tokens: Some(16_384),
toolshim: true,
toolshim_model: Some("qwen2.5-coder".to_string()),
request_params: Some(HashMap::from([(
"anthropic_beta".to_string(),
serde_json::json!(["output-128k-2025-02-19"]),
)])),
reasoning: Some(false),
};
let switched =
build_switched_model_config("openai", "gpt-5.4", &current_model_config).unwrap();
let expected = goose_providers::model::ModelConfig::new("gpt-5.4")
.with_canonical_limits("openai")
.with_temperature(Some(0.25))
.with_toolshim(true)
.with_toolshim_model(Some("qwen2.5-coder".to_string()));
assert_eq!(switched.model_name, expected.model_name);
assert_eq!(switched.context_limit, expected.context_limit);
assert_eq!(switched.max_tokens, expected.max_tokens);
assert_eq!(switched.request_params, expected.request_params);
assert_eq!(switched.reasoning, expected.reasoning);
assert_eq!(switched.temperature, Some(0.25));
assert!(switched.toolshim);
assert_eq!(switched.toolshim_model.as_deref(), Some("qwen2.5-coder"));
}
#[test]
fn test_build_switched_model_config_detects_effort_suffix_change() {
let _guard = env_lock::lock_env([
("GOOSE_MAX_TOKENS", None::<&str>),
("GOOSE_TEMPERATURE", None::<&str>),
("GOOSE_CONTEXT_LIMIT", None::<&str>),
("GOOSE_TOOLSHIM", None::<&str>),
("GOOSE_TOOLSHIM_OLLAMA_MODEL", None::<&str>),
("GOOSE_THINKING_EFFORT", None::<&str>),
]);
let current = goose_providers::model::ModelConfig::new("gpt-5.4-high")
.with_canonical_limits("openai");
assert_eq!(current.model_name, "gpt-5.4");
assert_eq!(
current.thinking_effort(),
Some(goose_providers::thinking::ThinkingEffort::High)
);
let switched = build_switched_model_config("openai", "gpt-5.4", &current).unwrap();
assert_eq!(switched.model_name, current.model_name);
assert_ne!(switched.thinking_effort(), current.thinking_effort());
}
#[test]
fn test_split_command_args_windows_paths() {
assert_eq!(

View file

@ -10,6 +10,7 @@ use goose::providers::canonical::maybe_get_canonical_model;
#[cfg(target_os = "windows")]
use goose::subprocess::SubprocessExt;
use goose::utils::safe_truncate;
use goose_providers::conversation::token_usage::Usage;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use rmcp::model::{CallToolRequestParams, JsonObject, PromptArgument};
use serde_json::Value;
@ -247,7 +248,8 @@ pub fn render_message(message: &Message, debug: bool) {
}
MessageContent::SystemNotification(notification) => {
match notification.notification_type {
SystemNotificationType::ThinkingMessage => {
SystemNotificationType::ThinkingMessage
| SystemNotificationType::ProgressMessage => {
show_thinking();
set_thinking_message(&notification.msg);
}
@ -329,7 +331,8 @@ pub fn render_message_streaming(
}
MessageContent::SystemNotification(notification) => {
match notification.notification_type {
SystemNotificationType::ThinkingMessage => {
SystemNotificationType::ThinkingMessage
| SystemNotificationType::ProgressMessage => {
show_thinking();
set_thinking_message(&notification.msg);
}
@ -1414,31 +1417,33 @@ pub fn display_context_usage(total_tokens: usize, context_limit: usize) {
);
}
fn estimate_cost_usd(
provider: &str,
model: &str,
input_tokens: usize,
output_tokens: usize,
) -> Option<f64> {
fn estimate_cost_usd(provider: &str, model: &str, usage: &Usage) -> Option<f64> {
let canonical_model = maybe_get_canonical_model(provider, model)?;
let input_cost_per_token = canonical_model.cost.input? / 1_000_000.0;
let output_cost_per_token = canonical_model.cost.output? / 1_000_000.0;
let input_cost = input_cost_per_token * input_tokens as f64;
let output_cost = output_cost_per_token * output_tokens as f64;
Some(input_cost + output_cost)
canonical_model.cost.estimate_cost(usage)
}
/// Display cost information, if price data is available.
pub fn display_cost_usage(provider: &str, model: &str, input_tokens: usize, output_tokens: usize) {
if let Some(cost) = estimate_cost_usd(provider, model, input_tokens, output_tokens) {
pub fn display_cost_usage(provider: &str, model: &str, usage: &Usage) {
if let Some(cost) = estimate_cost_usd(provider, model, usage) {
use console::style;
let input_tokens = usage.input_tokens.unwrap_or(0);
let output_tokens = usage.output_tokens.unwrap_or(0);
let cache_read = usage.cache_read_input_tokens.unwrap_or(0);
let cache_write = usage.cache_write_input_tokens.unwrap_or(0);
let cache_breakdown = match (cache_read, cache_write) {
(0, 0) => String::new(),
(read, 0) => format!(" ({} cache read)", read),
(0, write) => format!(" ({} cache write)", write),
(read, write) => format!(" ({} cache read, {} cache write)", read, write),
};
eprintln!(
"Cost: {} USD ({} tokens: in {}, out {})",
"Cost: {} USD ({} tokens: in {}{}, out {})",
style(format!("${:.4}", cost)).cyan(),
input_tokens + output_tokens,
input_tokens,
cache_breakdown,
output_tokens
);
}

View file

@ -524,10 +524,8 @@ impl MarkdownBuffer {
}
}
"]" => {}
")" => {
if state.in_link_url {
state.in_link_url = false;
}
")" if state.in_link_url => {
state.in_link_url = false;
}
_ => {}
}

View file

@ -1,4 +1,4 @@
use rand::seq::SliceRandom;
use rand::seq::IndexedRandom;
/// Extended list of playful thinking messages including both goose and general AI actions
const THINKING_MESSAGES: &[&str] = &[
@ -215,6 +215,6 @@ const THINKING_MESSAGES: &[&str] = &[
/// Returns a random thinking message from the extended list
pub fn get_random_thinking_message() -> &'static str {
THINKING_MESSAGES
.choose(&mut rand::thread_rng())
.choose(&mut rand::rng())
.unwrap_or(&THINKING_MESSAGES[0])
}

View file

@ -0,0 +1,21 @@
[package]
name = "goose-download-manager"
version = "0.1.0-alpha.0"
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
description.workspace = true
[lints]
workspace = true
[dependencies]
anyhow = { workspace = true }
once_cell = { workspace = true }
reqwest = { workspace = true, features = ["stream"] }
serde = { workspace = true }
tokio = { workspace = true, features = ["fs", "io-util", "time"] }
tracing = { workspace = true }
utoipa = { workspace = true }

View file

@ -0,0 +1,691 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use tokio::io::AsyncWriteExt;
use tracing::info;
use utoipa::ToSchema;
fn partial_path_for(destination: &Path) -> PathBuf {
destination.with_extension(
destination
.extension()
.map(|e| format!("{}.part", e.to_string_lossy()))
.unwrap_or_else(|| "part".to_string()),
)
}
/// Remove orphaned `.part` files in the given directory (and one level of subdirectories).
/// Preserves `.part` files whose final destination is in `registered_paths` so that
/// in-progress shard downloads can resume after a restart.
pub fn cleanup_partial_downloads(
dir: &Path,
registered_paths: &std::collections::HashSet<PathBuf>,
) {
let should_keep = |part_path: &Path| -> bool {
// Derive the final path by stripping the trailing ".part" extension
let final_path = part_path.with_extension("");
registered_paths.contains(&final_path)
};
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().is_some_and(|e| e == "part") && !should_keep(&path) {
let _ = std::fs::remove_file(&path);
}
if path.is_dir() {
if let Ok(sub_entries) = std::fs::read_dir(&path) {
for sub in sub_entries.flatten() {
let sub_path = sub.path();
if sub_path.extension().is_some_and(|e| e == "part")
&& !should_keep(&sub_path)
{
let _ = std::fs::remove_file(&sub_path);
}
}
}
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct DownloadProgress {
/// Model ID being downloaded
pub model_id: String,
/// Download status
pub status: DownloadStatus,
/// Bytes downloaded so far
pub bytes_downloaded: u64,
/// Total bytes to download
pub total_bytes: u64,
/// Download progress percentage (0-100)
pub progress_percent: f32,
/// Download speed in bytes per second
pub speed_bps: Option<u64>,
/// Estimated time remaining in seconds
pub eta_seconds: Option<u64>,
/// Error message if failed
pub error: Option<String>,
/// Whether the background download task has exited
#[serde(skip)]
pub task_exited: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum DownloadStatus {
Downloading,
Completed,
Failed,
Cancelled,
}
type DownloadMap = Arc<Mutex<HashMap<String, DownloadProgress>>>;
pub struct DownloadManager {
downloads: DownloadMap,
}
impl Default for DownloadManager {
fn default() -> Self {
Self::new()
}
}
impl DownloadManager {
pub fn new() -> Self {
Self {
downloads: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn get_progress(&self, model_id: &str) -> Option<DownloadProgress> {
self.downloads.lock().ok()?.get(model_id).cloned()
}
pub fn is_downloading(&self, model_id: &str) -> bool {
self.get_progress(model_id)
.is_some_and(|progress| progress.status == DownloadStatus::Downloading)
}
pub fn list_progress(&self) -> Vec<DownloadProgress> {
self.downloads
.lock()
.map(|downloads| downloads.values().cloned().collect())
.unwrap_or_default()
}
pub fn set_progress(&self, progress: DownloadProgress) {
if let Ok(mut downloads) = self.downloads.lock() {
downloads.insert(progress.model_id.clone(), progress);
}
}
pub fn reserve_download(&self, progress: DownloadProgress) -> Result<bool> {
let mut downloads = self
.downloads
.lock()
.map_err(|_| anyhow::anyhow!("Failed to acquire lock"))?;
if let Some(existing) = downloads.get(&progress.model_id) {
if existing.status == DownloadStatus::Downloading
|| (existing.status == DownloadStatus::Cancelled && !existing.task_exited)
{
return Ok(false);
}
}
downloads.insert(progress.model_id.clone(), progress);
Ok(true)
}
pub fn update_progress(&self, model_id: &str, update: impl FnOnce(&mut DownloadProgress)) {
if let Ok(mut downloads) = self.downloads.lock() {
if let Some(progress) = downloads.get_mut(model_id) {
update(progress);
}
}
}
pub fn cancel_download(&self, model_id: &str) -> Result<()> {
let mut downloads = self
.downloads
.lock()
.map_err(|_| anyhow::anyhow!("Failed to acquire lock"))?;
if let Some(progress) = downloads.get_mut(model_id) {
progress.status = DownloadStatus::Cancelled;
Ok(())
} else {
anyhow::bail!("Download not found")
}
}
pub async fn download_model(
&self,
model_id: String,
url: String,
destination: PathBuf,
on_complete: Option<Box<dyn FnOnce() + Send + 'static>>,
) -> Result<()> {
self.download_model_sharded(model_id, vec![(url, destination)], 0, on_complete)
.await
}
pub async fn download_model_with_bearer_token(
&self,
model_id: String,
url: String,
destination: PathBuf,
bearer_token: Option<String>,
on_complete: Option<Box<dyn FnOnce() + Send + 'static>>,
) -> Result<()> {
self.download_model_sharded_with_bearer_token(
model_id,
vec![(url, destination)],
0,
bearer_token,
on_complete,
)
.await
}
pub async fn download_model_sharded(
&self,
model_id: String,
files: Vec<(String, PathBuf)>,
total_size_hint: u64,
on_complete: Option<Box<dyn FnOnce() + Send + 'static>>,
) -> Result<()> {
self.download_model_sharded_with_bearer_token(
model_id,
files,
total_size_hint,
None,
on_complete,
)
.await
}
pub async fn download_model_sharded_with_bearer_token(
&self,
model_id: String,
files: Vec<(String, PathBuf)>,
total_size_hint: u64,
bearer_token: Option<String>,
on_complete: Option<Box<dyn FnOnce() + Send + 'static>>,
) -> Result<()> {
info!(model_id = %model_id, file_count = files.len(), "Starting model download");
{
let mut downloads = self
.downloads
.lock()
.map_err(|_| anyhow::anyhow!("Failed to acquire lock"))?;
if let Some(existing) = downloads.get(&model_id) {
if existing.status == DownloadStatus::Downloading {
anyhow::bail!("Download already in progress");
}
if existing.status == DownloadStatus::Cancelled && !existing.task_exited {
anyhow::bail!(
"Download is being cancelled; wait for it to finish before restarting"
);
}
}
downloads.insert(
model_id.clone(),
DownloadProgress {
model_id: model_id.clone(),
status: DownloadStatus::Downloading,
bytes_downloaded: 0,
total_bytes: total_size_hint,
progress_percent: 0.0,
speed_bps: None,
eta_seconds: None,
error: None,
task_exited: false,
},
);
}
// Create parent directories for all files
for (_, dest) in &files {
if let Some(parent) = dest.parent() {
tokio::fs::create_dir_all(parent)
.await
.map_err(|e| anyhow::anyhow!("Failed to create directory: {}", e))?;
}
}
let downloads = self.downloads.clone();
let model_id_clone = model_id.clone();
let files_for_cleanup: Vec<PathBuf> = files.iter().map(|(_, d)| d.clone()).collect();
tokio::spawn(async move {
let result = Self::download_files_sequentially(
&files,
&downloads,
&model_id_clone,
bearer_token.as_deref(),
)
.await;
match result {
Ok(_) => {
info!(model_id = %model_id_clone, "Download completed successfully");
if let Ok(mut downloads) = downloads.lock() {
if let Some(progress) = downloads.get_mut(&model_id_clone) {
progress.status = DownloadStatus::Completed;
progress.progress_percent = 100.0;
progress.task_exited = true;
}
}
if let Some(callback) = on_complete {
callback();
}
}
Err(e) => {
for dest in &files_for_cleanup {
let partial = partial_path_for(dest);
let _ = tokio::fs::remove_file(&partial).await;
}
if let Ok(mut downloads) = downloads.lock() {
if let Some(progress) = downloads.get_mut(&model_id_clone) {
if progress.status != DownloadStatus::Cancelled {
progress.status = DownloadStatus::Failed;
}
progress.error = Some(e.to_string());
progress.task_exited = true;
}
}
}
}
});
Ok(())
}
const MAX_RETRIES: u32 = 10;
const RETRY_BASE_DELAY: std::time::Duration = std::time::Duration::from_secs(2);
const RETRY_MAX_DELAY: std::time::Duration = std::time::Duration::from_secs(60);
async fn cancellable_sleep(
delay: std::time::Duration,
downloads: &DownloadMap,
model_id: &str,
) -> Result<(), anyhow::Error> {
let check_interval = std::time::Duration::from_millis(500);
let start = std::time::Instant::now();
while start.elapsed() < delay {
if Self::is_cancelled(downloads, model_id) {
anyhow::bail!("Download cancelled");
}
let remaining = delay.saturating_sub(start.elapsed());
tokio::time::sleep(std::cmp::min(check_interval, remaining)).await;
}
Ok(())
}
fn is_cancelled(downloads: &DownloadMap, model_id: &str) -> bool {
if let Ok(downloads) = downloads.lock() {
if let Some(progress) = downloads.get(model_id) {
return progress.status == DownloadStatus::Cancelled;
}
}
false
}
#[allow(clippy::too_many_arguments)]
/// Download multiple files sequentially, tracking cumulative progress under one model_id.
async fn download_files_sequentially(
files: &[(String, PathBuf)],
downloads: &DownloadMap,
model_id: &str,
bearer_token: Option<&str>,
) -> Result<(), anyhow::Error> {
let client = reqwest::Client::builder()
.connect_timeout(std::time::Duration::from_secs(30))
.read_timeout(std::time::Duration::from_secs(120))
.build()?;
// HEAD each file to get accurate total size. Only replace the hint if
// every file returned a size; partial results would underestimate.
let mut total: u64 = 0;
let mut all_resolved = true;
for (url, _) in files {
let size = Self::apply_bearer_token(client.head(url), bearer_token)
.send()
.await
.ok()
.and_then(|r| r.content_length())
.unwrap_or(0);
if size == 0 {
all_resolved = false;
}
total += size;
}
if all_resolved && total > 0 {
if let Ok(mut dl) = downloads.lock() {
if let Some(progress) = dl.get_mut(model_id) {
progress.total_bytes = total;
}
}
}
let start_time = std::time::Instant::now();
let mut cumulative_bytes: u64 = 0;
// Account for already-downloaded shards
for (_, dest) in files {
let partial = partial_path_for(dest);
if dest.exists() {
if let Ok(meta) = tokio::fs::metadata(dest).await {
cumulative_bytes += meta.len();
}
} else if partial.exists() {
if let Ok(meta) = tokio::fs::metadata(&partial).await {
cumulative_bytes += meta.len();
}
}
}
let bytes_at_start = cumulative_bytes;
for (url, destination) in files {
if Self::is_cancelled(downloads, model_id) {
anyhow::bail!("Download cancelled");
}
// Skip already-completed shards
if destination.exists() {
continue;
}
Self::download_one_file(
&client,
url,
destination,
downloads,
model_id,
&mut cumulative_bytes,
start_time,
bytes_at_start,
bearer_token,
)
.await?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn download_one_file(
client: &reqwest::Client,
url: &str,
destination: &Path,
downloads: &DownloadMap,
model_id: &str,
cumulative_bytes: &mut u64,
start_time: std::time::Instant,
bytes_at_start: u64,
bearer_token: Option<&str>,
) -> Result<(), anyhow::Error> {
let partial_path = partial_path_for(destination);
let mut retries = 0u32;
let mut file_bytes: u64 = if partial_path.exists() {
tokio::fs::metadata(&partial_path).await?.len()
} else {
0
};
// Get this file's total size
let mut file_total: u64 = Self::apply_bearer_token(client.head(url), bearer_token)
.send()
.await
.ok()
.and_then(|r| r.content_length())
.unwrap_or(0);
// If partial matches expected size exactly, promote it
if file_total > 0 && file_bytes == file_total {
tokio::fs::rename(&partial_path, destination).await?;
// cumulative_bytes already accounts for this file from the pre-scan
return Ok(());
}
// If partial is oversized or remote changed, discard and re-download
if file_total > 0 && file_bytes > file_total {
info!(model_id = %model_id, file_bytes, file_total, "Partial file oversized, re-downloading");
*cumulative_bytes = cumulative_bytes.saturating_sub(file_bytes);
file_bytes = 0;
let _ = tokio::fs::remove_file(&partial_path).await;
}
loop {
if Self::is_cancelled(downloads, model_id) {
let _ = tokio::fs::remove_file(&partial_path).await;
anyhow::bail!("Download cancelled");
}
let mut request = Self::apply_bearer_token(client.get(url), bearer_token);
if file_bytes > 0 {
request = request.header("Range", format!("bytes={}-", file_bytes));
}
let response = match request.send().await {
Ok(r) => r,
Err(e) => {
if retries >= Self::MAX_RETRIES {
anyhow::bail!("Download failed after {} retries: {}", retries, e);
}
retries += 1;
let delay = std::cmp::min(
Self::RETRY_BASE_DELAY * 2u32.saturating_pow(retries - 1),
Self::RETRY_MAX_DELAY,
);
info!(model_id = %model_id, retry = retries, delay_secs = ?delay.as_secs(), error = %e, "Retrying download after connection error");
Self::cancellable_sleep(delay, downloads, model_id).await?;
continue;
}
};
let status = response.status();
if status == reqwest::StatusCode::RANGE_NOT_SATISFIABLE {
if file_total > 0 && file_bytes == file_total {
break;
}
*cumulative_bytes = cumulative_bytes.saturating_sub(file_bytes);
file_bytes = 0;
let _ = tokio::fs::remove_file(&partial_path).await;
continue;
}
if !status.is_success() && status != reqwest::StatusCode::PARTIAL_CONTENT {
let is_transient = status.is_server_error()
|| status == reqwest::StatusCode::REQUEST_TIMEOUT
|| status == reqwest::StatusCode::TOO_MANY_REQUESTS;
if !is_transient || retries >= Self::MAX_RETRIES {
anyhow::bail!("Failed to download: HTTP {}", status);
}
retries += 1;
let delay = std::cmp::min(
Self::RETRY_BASE_DELAY * 2u32.saturating_pow(retries - 1),
Self::RETRY_MAX_DELAY,
);
info!(model_id = %model_id, retry = retries, http_status = %status, "Retrying download after transient HTTP error");
Self::cancellable_sleep(delay, downloads, model_id).await?;
continue;
}
if file_bytes > 0 && status == reqwest::StatusCode::OK {
info!(model_id = %model_id, "Server ignored Range header, restarting file from scratch");
// Subtract already-counted partial bytes from cumulative
*cumulative_bytes = cumulative_bytes.saturating_sub(file_bytes);
file_bytes = 0;
let _ = tokio::fs::remove_file(&partial_path).await;
}
// If HEAD didn't return this file's size, learn it from the GET response.
// This block only fires once per file (file_total stays non-zero after),
// so retries don't double-count. Since download_files_sequentially's HEAD
// pass contributed 0 for this file, we add the discovered size to the
// shared total so progress/ETA are accurate.
if file_total == 0 {
let new_file_total = if file_bytes > 0 {
response
.headers()
.get("content-range")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.rsplit('/').next())
.and_then(|s| s.parse::<u64>().ok())
} else {
response.content_length()
};
if let Some(t) = new_file_total {
file_total = t;
if let Ok(mut dl) = downloads.lock() {
if let Some(progress) = dl.get_mut(model_id) {
progress.total_bytes = progress.total_bytes.saturating_add(t);
}
}
}
}
let mut file = tokio::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&partial_path)
.await?;
let file_len = tokio::fs::metadata(&partial_path).await?.len();
if file_len != file_bytes {
file.set_len(file_bytes).await?;
}
let mut stream_error = false;
let mut resp = response;
loop {
let chunk_result = resp.chunk().await;
match chunk_result {
Ok(Some(chunk)) => {
if Self::is_cancelled(downloads, model_id) {
let _ = tokio::fs::remove_file(&partial_path).await;
anyhow::bail!("Download cancelled");
}
file.write_all(&chunk).await?;
let chunk_len = chunk.len() as u64;
file_bytes += chunk_len;
*cumulative_bytes += chunk_len;
let elapsed = start_time.elapsed().as_secs_f64();
let bytes_this_session = cumulative_bytes.saturating_sub(bytes_at_start);
let speed_bps = if elapsed > 0.0 {
Some((bytes_this_session as f64 / elapsed) as u64)
} else {
None
};
let current_total = if let Ok(dl) = downloads.lock() {
dl.get(model_id).map(|p| p.total_bytes).unwrap_or(0)
} else {
0
};
let eta_seconds = if let Some(speed) = speed_bps {
if speed > 0 && current_total > 0 {
Some(current_total.saturating_sub(*cumulative_bytes) / speed)
} else {
None
}
} else {
None
};
if let Ok(mut dl) = downloads.lock() {
if let Some(progress) = dl.get_mut(model_id) {
progress.bytes_downloaded = *cumulative_bytes;
progress.progress_percent = if current_total > 0 {
(*cumulative_bytes as f64 / current_total as f64 * 100.0) as f32
} else {
0.0
};
progress.speed_bps = speed_bps;
progress.eta_seconds = eta_seconds;
}
}
}
Ok(None) => break,
Err(e) => {
info!(model_id = %model_id, bytes = *cumulative_bytes, error = %e, "Download stream interrupted, will retry");
stream_error = true;
break;
}
}
}
file.flush().await?;
drop(file);
if stream_error {
if retries >= Self::MAX_RETRIES {
anyhow::bail!(
"Download failed after {} retries due to stream interruption",
retries
);
}
retries += 1;
let delay = std::cmp::min(
Self::RETRY_BASE_DELAY * 2u32.saturating_pow(retries - 1),
Self::RETRY_MAX_DELAY,
);
info!(model_id = %model_id, retry = retries, delay_secs = ?delay.as_secs(), "Retrying download with resume");
Self::cancellable_sleep(delay, downloads, model_id).await?;
continue;
}
break;
}
tokio::fs::rename(&partial_path, destination).await?;
Ok(())
}
pub fn clear_completed(&self, model_id: &str) {
if let Ok(mut downloads) = self.downloads.lock() {
if let Some(progress) = downloads.get(model_id) {
let is_terminal = progress.status == DownloadStatus::Completed
|| progress.status == DownloadStatus::Failed
|| progress.status == DownloadStatus::Cancelled;
if is_terminal && progress.task_exited {
downloads.remove(model_id);
}
}
}
}
fn apply_bearer_token(
request: reqwest::RequestBuilder,
bearer_token: Option<&str>,
) -> reqwest::RequestBuilder {
if let Some(token) = bearer_token.filter(|token| !token.is_empty()) {
request.header("Authorization", format!("Bearer {}", token))
} else {
request
}
}
}
static DOWNLOAD_MANAGER: once_cell::sync::Lazy<DownloadManager> =
once_cell::sync::Lazy::new(DownloadManager::new);
pub fn get_download_manager() -> &'static DownloadManager {
&DOWNLOAD_MANAGER
}

View file

@ -0,0 +1,54 @@
[package]
name = "goose-local-inference"
version = "0.1.0-alpha.0"
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
description.workspace = true
[lints]
workspace = true
[features]
default = []
cuda = ["llama-cpp-2/cuda"]
vulkan = ["llama-cpp-2/vulkan"]
mlx = ["dep:safemlx", "dep:safemlx-lm", "dep:safemlx-lm-utils"]
[dependencies]
anyhow = { workspace = true }
async-stream = { workspace = true }
async-trait = { workspace = true }
base64 = { workspace = true }
chrono = { workspace = true }
etcetera = { workspace = true }
encoding_rs = { version = "0.8.35", default-features = false }
fs2 = { workspace = true }
futures = { workspace = true }
goose-download-manager = { version = "0.1.0-alpha.0", path = "../goose-download-manager" }
goose-provider-types = { version = "0.1.0-alpha.0", path = "../goose-provider-types", default-features = false }
goose-sdk-types = { version = "0.1.0-alpha.0", path = "../goose-sdk-types", default-features = false }
hf-hub = { version = "1.0.0-rc.1", default-features = false }
include_dir = { workspace = true }
llama-cpp-2 = { workspace = true }
llama-cpp-sys-2 = { workspace = true }
minijinja = { version = "2.18", default-features = false, features = ["loader", "multi_template", "serde"] }
reqwest = { workspace = true, features = ["json", "stream"] }
regex = { workspace = true }
rmcp = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["fs", "rt-multi-thread", "sync", "time"] }
tracing = { workspace = true }
utoipa = { workspace = true, features = ["chrono"] }
uuid = { workspace = true, features = ["v4", "std"] }
tempfile = { workspace = true }
safemlx = { default-features = false, features = ["accelerate", "metal", "safetensors"], optional = true, version = "0.1.2" }
safemlx-lm = { optional = true, version = "0.1.5" }
safemlx-lm-utils = { optional = true, version = "0.1.2" }
[target.'cfg(target_os = "macos")'.dependencies]
llama-cpp-2 = { workspace = true, features = ["sampler", "metal", "mtmd"] }

View file

@ -1,10 +1,10 @@
use rmcp::model::Tool;
use std::any::Any;
use crate::conversation::message::Message;
use crate::providers::errors::ProviderError;
use crate::providers::local_inference::local_model_registry::ModelSettings;
use crate::providers::utils::RequestLog;
use crate::local_model_registry::ModelSettings;
use goose_provider_types::conversation::message::Message;
use goose_provider_types::errors::ProviderError;
use goose_provider_types::request_log::RequestLogHandle;
use super::{ResolvedModelPaths, StreamSender};
@ -12,17 +12,22 @@ pub(super) trait BackendLoadedModel: Send {
fn as_any_mut(&mut self) -> &mut dyn Any;
}
#[cfg_attr(not(feature = "mlx"), allow(dead_code))]
pub(super) struct LocalGenerationRequest<'a> {
pub model_name: String,
pub system: &'a str,
pub messages: &'a [Message],
pub tools: &'a [Tool],
pub settings: &'a ModelSettings,
pub temperature: Option<f32>,
pub max_tokens: Option<i32>,
pub context_limit: usize,
pub model_load_ms: Option<u64>,
pub resolved_model: &'a ResolvedModelPaths,
pub draft_model_path: Option<std::path::PathBuf>,
pub message_id: &'a str,
pub tx: &'a StreamSender,
pub log: &'a mut RequestLog,
pub log: &'a mut Option<Box<dyn RequestLogHandle>>,
}
pub(super) trait LocalInferenceBackend: Send + Sync {

View file

@ -0,0 +1,30 @@
use anyhow::Result;
use std::sync::OnceLock;
pub type StringParamResolver = fn(&'static str) -> Result<Option<String>>;
pub type BoolParamResolver = fn(&'static str) -> Result<Option<bool>>;
static STRING_PARAM_RESOLVER: OnceLock<StringParamResolver> = OnceLock::new();
static BOOL_PARAM_RESOLVER: OnceLock<BoolParamResolver> = OnceLock::new();
pub fn set_string_param_resolver(resolve_param: StringParamResolver) {
let _ = STRING_PARAM_RESOLVER.set(resolve_param);
}
pub fn set_bool_param_resolver(resolve_param: BoolParamResolver) {
let _ = BOOL_PARAM_RESOLVER.set(resolve_param);
}
pub fn string_param(key: &'static str) -> Result<Option<String>> {
match STRING_PARAM_RESOLVER.get() {
Some(resolve_param) => resolve_param(key),
None => Ok(None),
}
}
pub fn bool_param(key: &'static str) -> Result<Option<bool>> {
match BOOL_PARAM_RESOLVER.get() {
Some(resolve_param) => resolve_param(key),
None => Ok(None),
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,64 @@
use crate::{config_resolver, paths::Paths};
use anyhow::Result;
use chrono::{DateTime, Utc};
use futures::future::BoxFuture;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
pub const HUGGINGFACE_TOKEN_SECRET_KEY: &str = "HF_TOKEN";
pub const HUGGINGFACE_OAUTH_CACHE_PATH: &str = "huggingface/oauth/tokens.json";
pub type TokenResolver = fn() -> BoxFuture<'static, Result<Option<String>>>;
static TOKEN_RESOLVER: OnceLock<TokenResolver> = OnceLock::new();
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HuggingFaceTokenData {
pub access_token: String,
#[serde(default)]
pub refresh_token: Option<String>,
#[serde(default)]
pub expires_at: Option<DateTime<Utc>>,
}
impl HuggingFaceTokenData {
pub fn is_expired(&self) -> bool {
self.expires_at
.is_some_and(|expires_at| expires_at <= Utc::now())
}
}
pub fn oauth_cache_path() -> PathBuf {
Paths::in_config_dir(HUGGINGFACE_OAUTH_CACHE_PATH)
}
fn load_oauth_token_from_path(path: &Path) -> Option<HuggingFaceTokenData> {
let contents = std::fs::read_to_string(path).ok()?;
serde_json::from_str(&contents).ok()
}
pub fn usable_oauth_token() -> Option<String> {
let token = load_oauth_token_from_path(&oauth_cache_path())?;
(!token.is_expired()).then_some(token.access_token)
}
pub fn hf_token_secret() -> Result<Option<String>> {
Ok(config_resolver::string_param(HUGGINGFACE_TOKEN_SECRET_KEY)?
.filter(|token| !token.trim().is_empty()))
}
pub fn set_token_resolver(resolve_token: TokenResolver) {
let _ = TOKEN_RESOLVER.set(resolve_token);
}
pub async fn resolve_token_async() -> Result<Option<String>> {
if let Some(resolve_token) = TOKEN_RESOLVER.get() {
return resolve_token().await;
}
if let Some(token) = usable_oauth_token() {
return Ok(Some(token));
}
hf_token_secret()
}

View file

@ -0,0 +1,950 @@
pub mod config_resolver;
pub use goose_download_manager as download_manager;
pub mod huggingface_auth;
pub mod paths;
pub mod prompt_template;
pub mod provider_utils;
mod backend;
pub mod hf_models;
mod llamacpp;
pub mod local_model_registry;
pub mod management;
mod mlx;
pub(crate) mod multimodal;
#[cfg(feature = "mlx")]
mod native_tool_parsing;
pub(crate) mod thinking_output;
#[cfg(feature = "mlx")]
mod tool_emulation;
mod tool_parsing;
use anyhow::Result;
use async_stream::try_stream;
use async_trait::async_trait;
use backend::{BackendLoadedModel, LocalInferenceBackend};
use goose_provider_types::base::{MessageStream, Provider, ProviderDescriptor, ProviderMetadata};
use goose_provider_types::conversation::message::{
Message, MessageContent, SystemNotificationType,
};
use goose_provider_types::conversation::token_usage::{ProviderUsage, Usage};
use goose_provider_types::errors::ProviderError;
use goose_provider_types::images::ImageFormat;
use goose_provider_types::model::ModelConfig;
use goose_provider_types::request_log::{start_log, LoggerHandleExt, RequestLogHandle};
use llamacpp::{LlamaCppBackend, LLAMACPP_BACKEND_ID};
use local_model_registry::ChatTemplate;
use mlx::{MlxBackend, MLX_BACKEND_ID};
use rmcp::model::Tool;
use serde_json::{json, Value};
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::sync::{Arc, Mutex as StdMutex};
use tokio::sync::{Mutex, Notify};
use uuid::Uuid;
type ModelSlotHandle = Arc<ModelSlot>;
struct ModelSlot {
state: Mutex<ModelSlotState>,
notify: Notify,
}
enum ModelSlotState {
Empty,
Loading,
Loaded(Box<dyn BackendLoadedModel>),
}
impl ModelSlot {
fn new() -> Self {
Self {
state: Mutex::new(ModelSlotState::Empty),
notify: Notify::new(),
}
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct ModelCacheKey {
backend_id: &'static str,
model_id: String,
chat_template: ChatTemplate,
}
impl ModelCacheKey {
fn new(
backend_id: &'static str,
model_id: impl Into<String>,
chat_template: ChatTemplate,
) -> Self {
Self {
backend_id,
model_id: model_id.into(),
chat_template,
}
}
}
pub struct InferenceRuntime {
models: StdMutex<HashMap<ModelCacheKey, ModelSlotHandle>>,
cold_load_lock: Mutex<()>,
backends: HashMap<&'static str, Arc<dyn LocalInferenceBackend>>,
}
pub fn builtin_chat_template_names() -> Vec<String> {
llamacpp::builtin_chat_template_names()
}
static RUNTIME: StdMutex<Option<Arc<InferenceRuntime>>> = StdMutex::new(None);
fn current_runtime() -> Option<Arc<InferenceRuntime>> {
RUNTIME.lock().expect("runtime lock poisoned").clone()
}
impl InferenceRuntime {
pub fn get_or_init() -> Result<Arc<Self>> {
let mut guard = RUNTIME.lock().expect("runtime lock poisoned");
if let Some(runtime) = guard.as_ref() {
return Ok(runtime.clone());
}
let llamacpp_backend: Arc<dyn LocalInferenceBackend> = Arc::new(LlamaCppBackend::new()?);
let mlx_backend: Arc<dyn LocalInferenceBackend> = Arc::new(MlxBackend::new());
let mut backends = HashMap::new();
backends.insert(LLAMACPP_BACKEND_ID, llamacpp_backend);
backends.insert(MLX_BACKEND_ID, mlx_backend);
let runtime = Arc::new(Self {
models: StdMutex::new(HashMap::new()),
cold_load_lock: Mutex::new(()),
backends,
});
*guard = Some(runtime.clone());
Ok(runtime)
}
fn default_backend(&self) -> &dyn LocalInferenceBackend {
self.backends
.get(LLAMACPP_BACKEND_ID)
.expect("default local inference backend registered")
.as_ref()
}
fn backend_for_model(
&self,
resolved: &ResolvedModelPaths,
) -> Result<Arc<dyn LocalInferenceBackend>, ProviderError> {
let backend_id = resolved
.backend_id
.as_deref()
.unwrap_or(LLAMACPP_BACKEND_ID);
self.backends.get(backend_id).cloned().ok_or_else(|| {
ProviderError::ExecutionError(format!(
"Local inference backend '{}' unavailable",
backend_id
))
})
}
fn get_or_create_model_slot(&self, key: ModelCacheKey) -> ModelSlotHandle {
let mut map = self.models.lock().expect("model cache lock poisoned");
map.entry(key)
.or_insert_with(|| Arc::new(ModelSlot::new()))
.clone()
}
fn model_slot(&self, key: &ModelCacheKey) -> Option<ModelSlotHandle> {
let map = self.models.lock().expect("model cache lock poisoned");
map.get(key).cloned()
}
fn other_model_slots(&self, keep_key: &ModelCacheKey) -> Vec<ModelSlotHandle> {
let map = self.models.lock().expect("model cache lock poisoned");
map.iter()
.filter(|(key, _)| *key != keep_key)
.map(|(_, slot)| slot.clone())
.collect()
}
}
pub async fn is_model_loaded(model_name: &str) -> Result<bool, ProviderError> {
let resolved = match resolve_model_path(model_name) {
Some(resolved) => resolved,
None => return Ok(false),
};
let runtime = InferenceRuntime::get_or_init().map_err(|error| {
ProviderError::ExecutionError(format!("Failed to initialize local inference: {error}"))
})?;
let backend = runtime.backend_for_model(&resolved)?;
let key = ModelCacheKey::new(
backend.id(),
model_name.to_string(),
resolved.settings.chat_template,
);
let Some(slot) = runtime.model_slot(&key) else {
return Ok(false);
};
let state = slot.state.lock().await;
Ok(matches!(*state, ModelSlotState::Loaded(_)))
}
pub async fn loaded_model_ids() -> Result<HashSet<String>, ProviderError> {
let Some(runtime) = current_runtime() else {
return Ok(HashSet::new());
};
let slots = {
let map = runtime.models.lock().expect("model cache lock poisoned");
map.iter()
.map(|(key, slot)| (key.model_id.clone(), slot.clone()))
.collect::<Vec<_>>()
};
let mut loaded = HashSet::new();
for (model_id, slot) in slots {
if let Ok(state) = slot.state.try_lock() {
if matches!(*state, ModelSlotState::Loaded(_)) {
loaded.insert(model_id);
}
} else {
loaded.insert(model_id);
}
}
Ok(loaded)
}
pub async fn evict_model(model_name: &str) -> Result<bool, ProviderError> {
let Some(runtime) = current_runtime() else {
return Ok(false);
};
let slots = {
let map = runtime.models.lock().expect("model cache lock poisoned");
map.iter()
.filter(|(key, _)| key.model_id == model_name)
.map(|(_, slot)| slot.clone())
.collect::<Vec<_>>()
};
let mut evicted = false;
for slot in slots {
let mut state = slot.state.lock().await;
if matches!(*state, ModelSlotState::Loaded(_)) {
*state = ModelSlotState::Empty;
evicted = true;
slot.notify.notify_waiters();
}
}
Ok(evicted)
}
const PROVIDER_NAME: &str = "local";
const DEFAULT_MODEL: &str = "bartowski/Llama-3.2-1B-Instruct-GGUF:Q4_K_M";
pub const LOCAL_LLM_MODEL_CONFIG_KEY: &str = "LOCAL_LLM_MODEL";
#[derive(Clone)]
pub(crate) struct ResolvedModelPaths {
pub model_path: PathBuf,
pub context_limit: usize,
pub settings: crate::local_model_registry::ModelSettings,
pub mmproj_path: Option<PathBuf>,
pub backend_id: Option<String>,
pub draft_model_path: Option<PathBuf>,
}
fn resolve_model_local_path(model_id: &str) -> Option<PathBuf> {
use crate::local_model_registry::get_registry;
get_registry()
.lock()
.ok()?
.get_model(model_id)
.map(|entry| entry.local_path.clone())
}
/// Resolve model path, context limit, settings, and mmproj path for a model ID from the registry.
fn resolve_model_path(model_id: &str) -> Option<ResolvedModelPaths> {
use crate::local_model_registry::{default_settings_for_model, get_registry};
if let Ok(registry) = get_registry().lock() {
if let Some(entry) = registry.get_model(model_id) {
let ctx = entry.settings.context_size.unwrap_or(0) as usize;
let mut settings = entry.settings.clone();
let defaults = default_settings_for_model(model_id);
settings.vision_capable = defaults.vision_capable;
settings.mmproj_size_bytes = entry.mmproj_size_bytes;
let mmproj_path = entry.mmproj_path.as_ref().filter(|p| p.exists()).cloned();
let backend_id = entry
.backend_id
.clone()
.or_else(|| settings.backend_id.clone());
let draft_model = settings
.draft_model
.clone()
.or_else(|| {
config_resolver::string_param("GOOSE_LOCAL_DRAFT_MODEL")
.ok()
.flatten()
})
.filter(|draft_model| draft_model != model_id);
let draft_model_path = draft_model.as_deref().and_then(resolve_model_local_path);
return Some(ResolvedModelPaths {
model_path: entry.local_path.clone(),
context_limit: ctx,
settings,
mmproj_path,
backend_id,
draft_model_path,
});
}
}
None
}
pub fn available_inference_memory_bytes(runtime: &InferenceRuntime) -> u64 {
runtime.default_backend().available_memory_bytes()
}
pub fn recommend_local_model(runtime: &InferenceRuntime) -> String {
use local_model_registry::{get_registry, is_featured_model, FEATURED_MODELS};
let available_memory = available_inference_memory_bytes(runtime);
if let Ok(registry) = get_registry().lock() {
let mut models: Vec<_> = registry
.list_models()
.iter()
.filter(|m| is_featured_model(&m.id) && m.size_bytes > 0)
.collect();
models.sort_by_key(|model| std::cmp::Reverse(model.size_bytes));
// Return largest that fits in available memory
for model in &models {
if available_memory >= model.size_bytes {
return model.id.clone();
}
}
// If nothing fits, return smallest
if let Some(smallest) = models.last() {
return smallest.id.clone();
}
}
// Fallback to first featured model
FEATURED_MODELS[0].spec.to_string()
}
fn build_openai_messages_json(
system: &str,
messages: &[Message],
media_marker: Option<&str>,
) -> String {
use goose_provider_types::formats::openai::format_messages;
let mut arr: Vec<Value> = vec![json!({"role": "system", "content": system})];
arr.extend(format_messages(messages, &ImageFormat::OpenAi));
strip_image_parts_from_messages(&mut arr);
if let Some(marker) = media_marker {
convert_text_media_markers(&mut arr, marker);
}
serde_json::to_string(&arr).unwrap_or_else(|_| "[]".to_string())
}
fn build_openai_text_messages_json(
system: &str,
messages: &[Message],
media_marker: Option<&str>,
) -> String {
let mut arr: Vec<Value> = vec![json!({"role": "system", "content": system})];
arr.extend(messages.iter().filter_map(|m| {
let content = extract_text_content(m);
if content.trim().is_empty() {
return None;
}
let role = match m.role {
rmcp::model::Role::User => "user",
rmcp::model::Role::Assistant => "assistant",
};
Some(json!({"role": role, "content": content}))
}));
if let Some(marker) = media_marker {
convert_text_media_markers(&mut arr, marker);
}
serde_json::to_string(&arr).unwrap_or_else(|_| "[]".to_string())
}
fn convert_text_media_markers(messages: &mut [Value], marker: &str) {
if marker.is_empty() {
return;
}
for msg in messages {
let Some(content) = msg.get_mut("content") else {
continue;
};
if let Some(text) = content.as_str() {
if let Some(parts) = split_media_marker_text(text, marker) {
*content = json!(parts);
}
continue;
}
let Some(content_parts) = content.as_array_mut() else {
continue;
};
let mut updated = Vec::new();
let mut changed = false;
for part in content_parts.iter() {
if part.get("type").and_then(|v| v.as_str()) == Some("text") {
if let Some(text) = part.get("text").and_then(|v| v.as_str()) {
if let Some(parts) = split_media_marker_text(text, marker) {
updated.extend(parts);
changed = true;
continue;
}
}
}
updated.push(part.clone());
}
if changed {
*content_parts = updated;
}
}
}
fn split_media_marker_text(text: &str, marker: &str) -> Option<Vec<Value>> {
let mut parts = Vec::new();
let mut rest = text;
let mut found_marker = false;
while let Some((before, after)) = rest.split_once(marker) {
found_marker = true;
let before = before.strip_suffix('\n').unwrap_or(before);
if !before.is_empty() {
parts.push(json!({"type": "text", "text": before}));
}
parts.push(json!({"type": "media_marker", "text": marker}));
rest = after;
rest = rest.strip_prefix('\n').unwrap_or(rest);
}
if !found_marker {
return None;
}
if !rest.is_empty() {
parts.push(json!({"type": "text", "text": rest}));
}
Some(parts)
}
/// Remove `image_url` content parts from OpenAI-format messages JSON, replacing
/// each with a text note. This prevents an FFI crash in llama.cpp which does not
/// accept `image_url` content-part types.
fn strip_image_parts_from_messages(messages: &mut [Value]) {
let mut stripped = false;
for msg in messages.iter_mut() {
if let Some(content) = msg.get_mut("content").and_then(|c| c.as_array_mut()) {
for part in content.iter_mut() {
if part.get("type").and_then(|t| t.as_str()) == Some("image_url") {
*part = json!({
"type": "text",
"text": "[Image attached — image input is not supported with the currently selected model]"
});
stripped = true;
}
}
}
}
if stripped {
tracing::warn!("Stripped image content parts from messages — vision encoder not available for this model");
}
}
/// Convert a message into plain text for the emulator path's chat history.
///
/// This is the emulator-path counterpart of [`format_messages`] used by the native
/// path. It reconstructs the text-based tool syntax that the emulator prompt teaches
/// the model:
///
/// - `ToolRequest` with a `"command"` argument → `$ command`
/// - `ToolRequest` with a `"code"` argument → `` ```execute_typescript\n…\n``` ``
/// - `ToolResponse` → `Command output:\n…`
///
/// Only `developer__shell` and `code_execution__execute_typescript` style tool calls are
/// recognized (by argument shape, not tool name). Tool calls from other extensions
/// (e.g. custom MCP tools made by a native-tool-calling model earlier in the
/// conversation) are silently dropped, since the emulator path has no syntax to
/// represent them.
fn extract_text_content(msg: &Message) -> String {
let mut parts = Vec::new();
for content in &msg.content {
match content {
MessageContent::Text(text) => {
let text = strip_info_messages(&text.text);
if !text.trim().is_empty() {
parts.push(text);
}
}
MessageContent::ToolRequest(req) => {
if let Ok(call) = &req.tool_call {
if let Some(cmd) = call
.arguments
.as_ref()
.and_then(|a| a.get("command"))
.and_then(|v| v.as_str())
{
parts.push(format!("$ {}", cmd));
} else if let Some(code) = call
.arguments
.as_ref()
.and_then(|a| a.get("code"))
.and_then(|v| v.as_str())
{
parts.push(format!("```execute_typescript\n{}\n```", code));
}
}
}
MessageContent::ToolResponse(response) => match &response.tool_result {
Ok(result) => {
let mut output_parts = Vec::new();
for content_item in &result.content {
if let Some(text_content) = content_item.as_text() {
output_parts.push(text_content.text.to_string());
}
}
if !output_parts.is_empty() {
parts.push(format!("Command output:\n{}", output_parts.join("\n")));
}
}
Err(e) => {
parts.push(format!("Command error: {}", e));
}
},
MessageContent::Image(_) => {
parts.push(
"[Image attached — image input is not supported with the currently selected model]"
.to_string(),
);
}
_ => {}
}
}
parts.join("\n")
}
fn strip_info_messages(text: &str) -> String {
let mut remaining = text;
let mut output = String::new();
while let Some((before, after_start)) = remaining.split_once("<info-msg>") {
output.push_str(before);
if let Some((_, after_end)) = after_start.split_once("</info-msg>") {
remaining = after_end;
} else {
remaining = "";
break;
}
}
output.push_str(remaining);
output.trim().to_string()
}
/// Build a `ProviderUsage` and write the request log entry.
fn finalize_usage(
log: &mut Option<Box<dyn RequestLogHandle>>,
model_name: String,
path_label: &str,
prompt_token_count: usize,
output_token_count: i32,
extra_log_fields: Option<(&str, &str)>,
) -> ProviderUsage {
let input_tokens = prompt_token_count as i32;
let total_tokens = input_tokens + output_token_count;
let usage = Usage::new(
Some(input_tokens),
Some(output_token_count),
Some(total_tokens),
);
let mut log_json = serde_json::json!({
"path": path_label,
"prompt_tokens": input_tokens,
"output_tokens": output_token_count,
});
if let Some((key, value)) = extra_log_fields {
log_json[key] = serde_json::json!(value);
}
let _ = log.write(&log_json, Some(&usage));
ProviderUsage::new(model_name, usage)
}
type StreamSender =
tokio::sync::mpsc::Sender<Result<(Option<Message>, Option<ProviderUsage>), ProviderError>>;
pub struct LocalInferenceProvider {
runtime: Arc<InferenceRuntime>,
name: String,
}
impl LocalInferenceProvider {
pub async fn from_env() -> Result<Self> {
let runtime = InferenceRuntime::get_or_init()?;
Ok(Self {
runtime,
name: PROVIDER_NAME.to_string(),
})
}
}
impl ProviderDescriptor for LocalInferenceProvider {
fn metadata() -> ProviderMetadata
where
Self: Sized,
{
use crate::local_model_registry::{get_registry, FEATURED_MODELS};
let mut known_models: Vec<&str> = FEATURED_MODELS.iter().map(|m| m.spec).collect();
// Add any registry models not already in the featured list
let mut dynamic_models = Vec::new();
if let Ok(registry) = get_registry().lock() {
for entry in registry.list_models() {
if !known_models.contains(&entry.id.as_str()) {
dynamic_models.push(entry.id.clone());
}
}
}
let dynamic_refs: Vec<&str> = dynamic_models.iter().map(|s| s.as_str()).collect();
known_models.extend(dynamic_refs);
ProviderMetadata::new(
PROVIDER_NAME,
"Local Inference",
"Local inference using quantized GGUF models (llama.cpp)",
DEFAULT_MODEL,
known_models,
"https://github.com/utilityai/llama-cpp-rs",
vec![],
)
}
}
#[async_trait]
impl Provider for LocalInferenceProvider {
fn get_name(&self) -> &str {
&self.name
}
async fn fetch_supported_models(&self) -> Result<Vec<String>, ProviderError> {
use crate::local_model_registry::get_registry;
let mut all_models: Vec<String> = Vec::new();
if let Ok(registry) = get_registry().lock() {
for entry in registry.list_models() {
all_models.push(entry.id.clone());
}
}
Ok(all_models)
}
async fn stream(
&self,
model_config: &ModelConfig,
system: &str,
messages: &[Message],
tools: &[Tool],
) -> Result<MessageStream, ProviderError> {
let resolved = resolve_model_path(&model_config.model_name).ok_or_else(|| {
ProviderError::ExecutionError(format!("Model not found: {}", model_config.model_name))
})?;
let backend = self.runtime.backend_for_model(&resolved)?;
let model_context_limit = resolved.context_limit;
// Allow request_params to override thinking
let mut model_settings = resolved.settings.clone();
if let Some(false) = model_config
.request_param::<bool>("enable_thinking")
.or_else(|| {
config_resolver::bool_param("GOOSE_LOCAL_ENABLE_THINKING")
.ok()
.flatten()
})
{
model_settings.enable_thinking = false;
}
let cache_key = ModelCacheKey::new(
backend.id(),
model_config.model_name.clone(),
model_settings.chat_template.clone(),
);
let model_slot = self.runtime.get_or_create_model_slot(cache_key.clone());
let runtime = self.runtime.clone();
let cache_key = cache_key.clone();
let model_arc = model_slot.clone();
let backend = backend.clone();
let model_name = model_config.model_name.clone();
let temperature = model_config.temperature;
let max_tokens = model_config.max_tokens;
let context_limit = model_context_limit;
let settings = model_settings;
let resolved_model = resolved.clone();
let system = system.to_string();
let messages = messages.to_vec();
let tools = tools.to_vec();
let log_payload = serde_json::json!({
"system": &system,
"messages": messages.iter().map(|m| {
serde_json::json!({
"role": match m.role { rmcp::model::Role::User => "user", rmcp::model::Role::Assistant => "assistant" },
"content": extract_text_content(m),
})
}).collect::<Vec<_>>(),
"tools": tools.iter().map(|t| &t.name).collect::<Vec<_>>(),
"settings": {
"tool_calling": settings.tool_calling,
"chat_template": settings.chat_template,
"context_size": settings.context_size,
"sampling": settings.sampling,
},
});
let (tx, mut rx) = tokio::sync::mpsc::channel::<
Result<(Option<Message>, Option<ProviderUsage>), ProviderError>,
>(32);
let mut log = start_log(model_config, &log_payload)?;
tokio::spawn(async move {
let mut model_load_ms = None;
// Ensure model is loaded — unload any other models first to free memory.
loop {
let state = model_slot.state.lock().await;
match &*state {
ModelSlotState::Loaded(_) => break,
ModelSlotState::Loading => {
let notified = model_slot.notify.notified();
drop(state);
notified.await;
}
ModelSlotState::Empty => {
drop(state);
let cold_load_guard = runtime.cold_load_lock.lock().await;
let mut state = model_slot.state.lock().await;
match &*state {
ModelSlotState::Loaded(_) => break,
ModelSlotState::Loading => {
let notified = model_slot.notify.notified();
drop(state);
drop(cold_load_guard);
notified.await;
continue;
}
ModelSlotState::Empty => {}
}
*state = ModelSlotState::Loading;
drop(state);
let loading_message = Message::assistant().with_system_notification(
SystemNotificationType::ProgressMessage,
format!("Loading local model {model_name}..."),
);
if tx.send(Ok((Some(loading_message), None))).await.is_err() {
let mut state = model_slot.state.lock().await;
*state = ModelSlotState::Empty;
model_slot.notify.notify_waiters();
return;
}
let other_model_slots = runtime.other_model_slots(&cache_key);
for slot in other_model_slots {
let mut other = slot.state.lock().await;
if matches!(*other, ModelSlotState::Loaded(_)) {
tracing::info!("Unloading previous model to free memory");
*other = ModelSlotState::Empty;
}
}
let model_id = model_name.clone();
let resolved_for_load = resolved_model.clone();
let settings_for_load = settings.clone();
let backend_for_load = backend.clone();
let load_started = std::time::Instant::now();
let loaded = match tokio::task::spawn_blocking(move || {
backend_for_load.load_model(
&model_id,
&resolved_for_load,
&settings_for_load,
)
})
.await
{
Ok(Ok(loaded)) => loaded,
Ok(Err(err)) => {
let mut state = model_slot.state.lock().await;
*state = ModelSlotState::Empty;
model_slot.notify.notify_waiters();
let _ = log.error(&err);
let _ = tx.send(Err(err)).await;
return;
}
Err(err) => {
let mut state = model_slot.state.lock().await;
*state = ModelSlotState::Empty;
model_slot.notify.notify_waiters();
let err = ProviderError::ExecutionError(err.to_string());
let _ = log.error(&err);
let _ = tx.send(Err(err)).await;
return;
}
};
let elapsed_ms =
u64::try_from(load_started.elapsed().as_millis()).unwrap_or(u64::MAX);
model_load_ms = Some(elapsed_ms);
tracing::info!(
backend = backend.id(),
model = %model_name,
model_load_ms = elapsed_ms,
"Loaded local inference model"
);
let _ = log.write(
&json!({
"path": "model_load",
"backend": backend.id(),
"model": &model_name,
"model_load_ms": elapsed_ms,
}),
None,
);
let mut state = model_slot.state.lock().await;
*state = ModelSlotState::Loaded(loaded);
model_slot.notify.notify_waiters();
drop(cold_load_guard);
break;
}
}
}
tokio::task::spawn_blocking(move || {
// Macro to log errors before sending them through the channel
macro_rules! send_err {
($err:expr) => {{
let err = $err;
let msg = match &err {
ProviderError::ExecutionError(s) => s.as_str(),
ProviderError::ContextLengthExceeded(s) => s.as_str(),
_ => "unknown error",
};
let _ = log.error(msg);
let _ = tx.blocking_send(Err(err));
return;
}};
}
let mut model_guard = model_arc.state.blocking_lock();
let loaded = match &mut *model_guard {
ModelSlotState::Loaded(loaded) => loaded.as_mut(),
ModelSlotState::Empty | ModelSlotState::Loading => {
send_err!(ProviderError::ExecutionError(
"Model not loaded".to_string()
));
}
};
let message_id = Uuid::new_v4().to_string();
let request = backend::LocalGenerationRequest {
model_name,
system: &system,
messages: &messages,
tools: &tools,
settings: &settings,
temperature,
max_tokens,
context_limit,
model_load_ms,
resolved_model: &resolved_model,
draft_model_path: resolved_model.draft_model_path.clone(),
message_id: &message_id,
tx: &tx,
log: &mut log,
};
let result = backend.generate(loaded, request);
if let Err(err) = result {
let msg = match &err {
ProviderError::ExecutionError(s) => s.as_str(),
ProviderError::ContextLengthExceeded(s) => s.as_str(),
_ => "unknown error",
};
let _ = log.error(msg);
let _ = tx.blocking_send(Err(err));
}
});
});
Ok(Box::pin(try_stream! {
while let Some(result) = rx.recv().await {
let item = result?;
yield item;
}
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn converts_marker_in_string_content_to_media_marker_part() {
let mut messages = vec![json!({
"role": "user",
"content": "look\n<__media__>\nclosely",
})];
convert_text_media_markers(&mut messages, "<__media__>");
assert_eq!(
messages[0]["content"],
json!([
{"type": "text", "text": "look"},
{"type": "media_marker", "text": "<__media__>"},
{"type": "text", "text": "closely"},
])
);
}
#[test]
fn converts_marker_inside_text_content_parts() {
let mut messages = vec![json!({
"role": "user",
"content": [
{"type": "text", "text": "<__media__>describe"},
{"type": "text", "text": "next"},
{"type": "media_marker", "text": "<__media__>"},
],
})];
convert_text_media_markers(&mut messages, "<__media__>");
assert_eq!(
messages[0]["content"],
json!([
{"type": "media_marker", "text": "<__media__>"},
{"type": "text", "text": "describe"},
{"type": "text", "text": "next"},
{"type": "media_marker", "text": "<__media__>"},
])
);
}
}

View file

@ -20,18 +20,16 @@
//! These are inherent to text-based tool emulation. Models with native tool-calling
//! support should use the `inference_native_tools` path instead.
use crate::conversation::message::{Message, MessageContent};
use crate::providers::errors::ProviderError;
use llama_cpp_2::model::AddBos;
use goose_provider_types::conversation::message::{Message, MessageContent};
use goose_provider_types::errors::ProviderError;
use rmcp::model::{CallToolRequestParams, Tool};
use serde_json::json;
use std::borrow::Cow;
use uuid::Uuid;
use super::super::{finalize_usage, StreamSender};
use super::super::{finalize_usage, thinking_output::ThinkingOutputFilter, StreamSender};
use super::inference_engine::{
create_and_prefill_context, create_and_prefill_multimodal, generation_loop,
validate_and_compute_context, GenerationContext, TokenAction,
generation_loop, prepare_generation, GenerationContext, StopSuffixTrimmer, TokenAction,
};
const SHELL_TOOL: &str = "developer__shell";
@ -355,56 +353,26 @@ fn send_emulator_action(
pub(super) fn generate_with_emulated_tools(
ctx: &mut GenerationContext<'_>,
code_mode_enabled: bool,
oai_messages_json: &str,
) -> Result<(), ProviderError> {
// Use oaicompat variant — its C++ wrapper catches exceptions that would
// otherwise abort the process when other native libs disturb the C++ ABI.
let prompt = ctx
.loaded
.model
.apply_chat_template_with_tools_oaicompat(
&ctx.loaded.template,
ctx.chat_messages,
None, // no tools for emulated path
None, // no json_schema
true, // add_generation_prompt
)
.map(|r| r.prompt)
.map_err(|e| {
ProviderError::ExecutionError(format!("Failed to apply chat template: {}", e))
})?;
let (mut llama_ctx, prompt_token_count, effective_ctx) = if !ctx.images.is_empty() {
create_and_prefill_multimodal(
ctx.loaded,
ctx.backend,
&prompt,
ctx.images,
ctx.context_limit,
ctx.settings,
)?
} else {
let tokens = ctx
.loaded
.model
.str_to_token(&prompt, AddBos::Never)
.map_err(|e| ProviderError::ExecutionError(e.to_string()))?;
let (ptc, ectx) = validate_and_compute_context(
ctx.loaded,
ctx.backend,
tokens.len(),
ctx.context_limit,
ctx.settings,
)?;
let lctx =
create_and_prefill_context(ctx.loaded, ctx.backend, &tokens, ectx, ctx.settings)?;
(lctx, ptc, ectx)
};
let prepared = prepare_generation(ctx, oai_messages_json, None, None)?;
let template_result = prepared.template_result;
let mut llama_ctx = prepared.llama_ctx;
let prompt_token_count = prepared.prompt_token_count;
let effective_ctx = prepared.effective_ctx;
let message_id = ctx.message_id;
let tx = ctx.tx;
let mut emulator_parser = StreamingEmulatorParser::new(code_mode_enabled);
let mut output_filter = ThinkingOutputFilter::new(
ctx.settings.enable_thinking,
&template_result.generation_prompt,
);
let mut stop_trimmer = StopSuffixTrimmer::new(&template_result.additional_stops);
let mut generated_text = String::new();
let mut tool_call_emitted = false;
let mut send_failed = false;
let mut stop_string_emitted = false;
let output_token_count = generation_loop(
&ctx.loaded.model,
@ -413,7 +381,10 @@ pub(super) fn generate_with_emulated_tools(
prompt_token_count,
effective_ctx,
|piece| {
let actions = emulator_parser.process_chunk(piece);
generated_text.push_str(piece);
let filtered = output_filter.push_text(piece);
let (content, stop_seen) = stop_trimmer.push(&filtered.content);
let actions = emulator_parser.process_chunk(&content);
for action in actions {
match send_emulator_action(&action, message_id, tx) {
Ok(is_tool) => {
@ -429,12 +400,47 @@ pub(super) fn generate_with_emulated_tools(
}
if tool_call_emitted {
Ok(TokenAction::Stop)
} else if stop_seen
|| template_result
.additional_stops
.iter()
.any(|stop| generated_text.ends_with(stop))
{
stop_string_emitted = true;
Ok(TokenAction::Stop)
} else {
Ok(TokenAction::Continue)
}
},
)?;
if !send_failed {
let filtered = output_filter.finish();
if !filtered.thinking.is_empty() {
let mut message = Message::assistant().with_thinking(filtered.thinking, "");
message.id = Some(message_id.to_string());
send_failed = tx.blocking_send(Ok((Some(message), None))).is_err();
}
if !send_failed {
let content = if stop_string_emitted {
String::new()
} else {
let (content, stop_seen) = stop_trimmer.push(&filtered.content);
let mut content = content;
if !stop_seen {
content.push_str(&stop_trimmer.finish());
}
content
};
for action in emulator_parser.process_chunk(&content) {
if send_emulator_action(&action, message_id, tx).is_err() {
send_failed = true;
break;
}
}
}
}
if !send_failed {
for action in emulator_parser.flush() {
if send_emulator_action(&action, message_id, tx).is_err() {
@ -474,6 +480,50 @@ mod tests {
parse_chunks(&[input], code_mode)
}
fn trim_chunks(chunks: &[&str], stops: &[String]) -> (String, bool) {
let mut trimmer = StopSuffixTrimmer::new(stops);
let mut output = String::new();
let mut stopped = false;
for chunk in chunks {
let (content, stop_seen) = trimmer.push(chunk);
output.push_str(&content);
if stop_seen {
stopped = true;
break;
}
}
if !stopped {
output.push_str(&trimmer.finish());
}
(output, stopped)
}
fn parse_with_seeded_thinking(
chunks: &[&str],
code_mode: bool,
) -> (String, Vec<EmulatorAction>) {
let mut output_filter = ThinkingOutputFilter::new(true, "<|assistant|><think>\n");
let mut parser = StreamingEmulatorParser::new(code_mode);
let mut thinking = String::new();
let mut actions = Vec::new();
for chunk in chunks {
let filtered = output_filter.push_text(chunk);
thinking.push_str(&filtered.thinking);
actions.extend(parser.process_chunk(&filtered.content));
}
let filtered = output_filter.finish();
thinking.push_str(&filtered.thinking);
actions.extend(parser.process_chunk(&filtered.content));
actions.extend(parser.flush());
(thinking, actions)
}
fn assert_text(action: &EmulatorAction, expected: &str) {
match action {
EmulatorAction::Text(t) => assert_eq!(t.trim(), expected.trim(), "text mismatch"),
@ -507,6 +557,24 @@ mod tests {
}
}
#[test]
fn stop_suffix_trimmer_strips_split_stop() {
let stops = vec!["<|eom_id|>".to_string()];
let (content, stopped) = trim_chunks(&["The answer", "<|e", "om_id|>"], &stops);
assert!(stopped);
assert_eq!(content, "The answer");
}
#[test]
fn stop_suffix_trimmer_flushes_partial_non_stop() {
let stops = vec!["<|eom_id|>".to_string()];
let (content, stopped) = trim_chunks(&["Use the <", " symbol"], &stops);
assert!(!stopped);
assert_eq!(content, "Use the < symbol");
}
#[test]
fn plain_text_no_tools() {
let actions = parse_all("Hello, world!", false);
@ -667,6 +735,25 @@ mod tests {
assert_shell(shells[0], "echo hello");
}
#[test]
fn thinking_seeded_from_generation_prompt_is_not_emulated_text() {
let (thinking, actions) =
parse_with_seeded_thinking(&["reasoning\n$ echo hidden\n</think>The answer."], false);
assert_eq!(thinking.trim(), "reasoning\n$ echo hidden");
assert!(actions
.iter()
.all(|action| matches!(action, EmulatorAction::Text(_))));
let text: String = actions
.iter()
.filter_map(|action| match action {
EmulatorAction::Text(text) => Some(text.as_str()),
_ => None,
})
.collect();
assert_eq!(text.trim(), "The answer.");
}
#[test]
fn execute_block_with_multiline_code() {
let input = "```execute_typescript\nasync function run() {\n const r = await Developer.shell({ command: \"ls\" });\n return r;\n}\n```\n";

View file

@ -1,12 +1,13 @@
use crate::providers::errors::ProviderError;
use crate::providers::local_inference::backend::LocalInferenceBackend;
use crate::providers::local_inference::local_model_registry::ModelSettings;
use crate::providers::local_inference::multimodal::ExtractedImage;
use crate::providers::utils::RequestLog;
use crate::backend::LocalInferenceBackend;
use crate::local_model_registry::ModelSettings;
use crate::multimodal::ExtractedImage;
use goose_provider_types::errors::ProviderError;
use goose_provider_types::request_log::{LoggerHandleExt, RequestLogHandle};
use llama_cpp_2::context::params::LlamaContextParams;
use llama_cpp_2::llama_batch::LlamaBatch;
use llama_cpp_2::model::{LlamaChatMessage, LlamaChatTemplate, LlamaModel};
use llama_cpp_2::model::{AddBos, ChatTemplateResult, LlamaChatTemplate, LlamaModel};
use llama_cpp_2::mtmd::{MtmdBitmap, MtmdContext, MtmdInputText};
use llama_cpp_2::openai::OpenAIChatTemplateParams;
use llama_cpp_2::sampling::LlamaSampler;
use std::num::NonZeroU32;
@ -16,23 +17,97 @@ use super::LlamaCppBackend;
pub(super) struct GenerationContext<'a> {
pub loaded: &'a LoadedModel,
pub backend: &'a LlamaCppBackend,
pub chat_messages: &'a [LlamaChatMessage],
pub template: &'a LlamaChatTemplate,
pub settings: &'a ModelSettings,
pub context_limit: usize,
pub model_name: String,
pub message_id: &'a str,
pub tx: &'a StreamSender,
pub log: &'a mut RequestLog,
pub log: &'a mut Option<Box<dyn RequestLogHandle>>,
pub images: &'a [ExtractedImage],
}
pub(super) struct LoadedModel {
pub model: LlamaModel,
pub template: LlamaChatTemplate,
pub templates: LoadedChatTemplates,
/// Multimodal context for vision models. None for text-only models.
pub mtmd_ctx: Option<MtmdContext>,
}
pub(super) struct LoadedChatTemplates {
pub default: Option<LlamaChatTemplate>,
pub tool_use: Option<LlamaChatTemplate>,
pub force_default: bool,
}
pub(super) struct PreparedGeneration<'model> {
pub template_result: ChatTemplateResult,
pub llama_ctx: llama_cpp_2::context::LlamaContext<'model>,
pub prompt_token_count: usize,
pub effective_ctx: usize,
}
pub(super) struct StopSuffixTrimmer {
pending: String,
stops: Vec<String>,
}
impl StopSuffixTrimmer {
pub(super) fn new(stops: &[String]) -> Self {
Self {
pending: String::new(),
stops: stops
.iter()
.filter(|stop| !stop.is_empty())
.cloned()
.collect(),
}
}
pub(super) fn push(&mut self, chunk: &str) -> (String, bool) {
if self.stops.is_empty() {
return (chunk.to_string(), false);
}
self.pending.push_str(chunk);
if let Some(stop) = self
.stops
.iter()
.filter(|stop| self.pending.ends_with(stop.as_str()))
.max_by_key(|stop| stop.len())
{
let emit_len = self.pending.len() - stop.len();
let _stop = self.pending.split_off(emit_len);
let emit = std::mem::take(&mut self.pending);
return (emit, true);
}
let hold_len = self
.pending
.char_indices()
.map(|(idx, _)| idx)
.chain(std::iter::once(self.pending.len()))
.filter(|idx| {
self.pending
.get(*idx..)
.is_some_and(|suffix| self.stops.iter().any(|stop| stop.starts_with(suffix)))
})
.map(|idx| self.pending.len() - idx)
.max()
.unwrap_or(0);
let emit_len = self.pending.len() - hold_len;
let keep = self.pending.split_off(emit_len);
let emit = std::mem::replace(&mut self.pending, keep);
(emit, false)
}
pub(super) fn finish(&mut self) -> String {
std::mem::take(&mut self.pending)
}
}
/// Estimate the maximum context length that can fit in available accelerator/CPU
/// memory based on the model's KV cache requirements.
///
@ -90,7 +165,7 @@ pub(super) fn estimate_max_context_for_memory(
}
pub(super) fn context_cap(
settings: &crate::providers::local_inference::local_model_registry::ModelSettings,
settings: &crate::local_model_registry::ModelSettings,
context_limit: usize,
n_ctx_train: usize,
memory_max_ctx: Option<usize>,
@ -120,7 +195,7 @@ pub(super) fn context_cap(
pub(super) fn effective_context_size(
prompt_token_count: usize,
settings: &crate::providers::local_inference::local_model_registry::ModelSettings,
settings: &crate::local_model_registry::ModelSettings,
context_limit: usize,
n_ctx_train: usize,
memory_max_ctx: Option<usize>,
@ -140,7 +215,7 @@ pub(super) fn effective_context_size(
pub(super) fn build_context_params(
ctx_size: u32,
settings: &crate::providers::local_inference::local_model_registry::ModelSettings,
settings: &crate::local_model_registry::ModelSettings,
) -> LlamaContextParams {
let mut params = LlamaContextParams::default().with_n_ctx(NonZeroU32::new(ctx_size));
@ -159,10 +234,8 @@ pub(super) fn build_context_params(
params
}
pub(super) fn build_sampler(
settings: &crate::providers::local_inference::local_model_registry::ModelSettings,
) -> LlamaSampler {
use crate::providers::local_inference::local_model_registry::SamplingConfig;
pub(super) fn build_sampler(settings: &crate::local_model_registry::ModelSettings) -> LlamaSampler {
use crate::local_model_registry::SamplingConfig;
let has_penalties = settings.repeat_penalty != 1.0
|| settings.frequency_penalty != 0.0
@ -215,7 +288,7 @@ pub(super) fn validate_and_compute_context(
backend: &LlamaCppBackend,
prompt_token_count: usize,
context_limit: usize,
settings: &crate::providers::local_inference::local_model_registry::ModelSettings,
settings: &crate::local_model_registry::ModelSettings,
) -> Result<(usize, usize), ProviderError> {
let n_ctx_train = loaded.model.n_ctx_train() as usize;
let mmproj_overhead = if loaded.mtmd_ctx.is_some() {
@ -256,7 +329,7 @@ pub(super) fn create_and_prefill_context<'model>(
backend: &LlamaCppBackend,
tokens: &[llama_cpp_2::token::LlamaToken],
effective_ctx: usize,
settings: &crate::providers::local_inference::local_model_registry::ModelSettings,
settings: &crate::local_model_registry::ModelSettings,
) -> Result<llama_cpp_2::context::LlamaContext<'model>, ProviderError> {
let ctx_params = build_context_params(effective_ctx as u32, settings);
let mut ctx = loaded
@ -349,6 +422,121 @@ pub(super) fn create_and_prefill_multimodal<'model>(
Ok((llama_ctx, prompt_token_count, effective_ctx))
}
pub(super) fn prepare_generation<'model>(
ctx: &mut GenerationContext<'model>,
oai_messages_json: &str,
full_tools_json: Option<&str>,
compact_tools_json: Option<&str>,
) -> Result<PreparedGeneration<'model>, ProviderError> {
let apply_template = |tools: Option<&str>| {
let params = OpenAIChatTemplateParams {
messages_json: oai_messages_json,
tools_json: tools,
tool_choice: None,
json_schema: None,
grammar: None,
reasoning_format: if ctx.settings.enable_thinking {
Some("auto")
} else {
None
},
chat_template_kwargs: None,
add_generation_prompt: true,
use_jinja: true,
parallel_tool_calls: false,
enable_thinking: ctx.settings.enable_thinking,
add_bos: false,
add_eos: false,
parse_tool_calls: true,
};
ctx.loaded
.model
.apply_chat_template_oaicompat(ctx.template, &params)
};
let min_generation_headroom = 512;
let n_ctx_train = ctx.loaded.model.n_ctx_train() as usize;
let mmproj_overhead = if ctx.loaded.mtmd_ctx.is_some() {
ctx.settings.mmproj_size_bytes
} else {
0
};
let memory_max_ctx =
estimate_max_context_for_memory(&ctx.loaded.model, ctx.backend, mmproj_overhead);
let cap = context_cap(ctx.settings, ctx.context_limit, n_ctx_train, memory_max_ctx);
let token_budget = cap.saturating_sub(min_generation_headroom);
let estimated_image_tokens = ctx.images.len() * ctx.settings.image_token_estimate;
let template_result = match apply_template(full_tools_json) {
Ok(r) => {
let token_count = ctx
.loaded
.model
.str_to_token(&r.prompt, AddBos::Never)
.map(|t| t.len())
.unwrap_or(0);
if token_count + estimated_image_tokens > token_budget {
apply_template(compact_tools_json).unwrap_or(r)
} else {
r
}
}
Err(e) => {
tracing::warn!(
error = %e,
"Failed to apply llama.cpp OpenAI-compatible chat template"
);
match apply_template(compact_tools_json) {
Ok(r) => r,
Err(compact_err) => {
return Err(ProviderError::ExecutionError(format!(
"Failed to apply chat template with llama.cpp's Jinja renderer. This usually means the selected built-in template name does not exist, the embedded or custom template is invalid, or the template is incompatible with the current message shape. Select a valid llama.cpp built-in template name, configure a custom inline Jinja template, or use a GGUF with valid tokenizer.chat_template metadata. Full tools error: {e}; compact tools error: {compact_err}"
)));
}
}
}
};
let _ = ctx.log.write(
&serde_json::json!({"applied_prompt": &template_result.prompt}),
None,
);
let (llama_ctx, prompt_token_count, effective_ctx) = if !ctx.images.is_empty() {
create_and_prefill_multimodal(
ctx.loaded,
ctx.backend,
&template_result.prompt,
ctx.images,
ctx.context_limit,
ctx.settings,
)?
} else {
let tokens = ctx
.loaded
.model
.str_to_token(&template_result.prompt, AddBos::Never)
.map_err(|e| ProviderError::ExecutionError(e.to_string()))?;
let (ptc, ectx) = validate_and_compute_context(
ctx.loaded,
ctx.backend,
tokens.len(),
ctx.context_limit,
ctx.settings,
)?;
let lctx =
create_and_prefill_context(ctx.loaded, ctx.backend, &tokens, ectx, ctx.settings)?;
(lctx, ptc, ectx)
};
Ok(PreparedGeneration {
template_result,
llama_ctx,
prompt_token_count,
effective_ctx,
})
}
/// Action to take after processing a generated token piece.
pub(super) enum TokenAction {
Continue,
@ -362,7 +550,7 @@ pub(super) enum TokenAction {
pub(super) fn generation_loop(
model: &LlamaModel,
ctx: &mut llama_cpp_2::context::LlamaContext<'_>,
settings: &crate::providers::local_inference::local_model_registry::ModelSettings,
settings: &crate::local_model_registry::ModelSettings,
prompt_token_count: usize,
effective_ctx: usize,
mut on_piece: impl FnMut(&str) -> Result<TokenAction, ProviderError>,
@ -424,7 +612,7 @@ pub(super) fn generation_loop(
#[cfg(test)]
mod tests {
use super::*;
use crate::providers::local_inference::local_model_registry::ModelSettings;
use crate::local_model_registry::ModelSettings;
fn default_settings() -> ModelSettings {
ModelSettings::default()

View file

@ -1,129 +1,33 @@
use crate::conversation::message::{Message, MessageContent};
use crate::providers::errors::ProviderError;
use llama_cpp_2::model::AddBos;
use llama_cpp_2::openai::OpenAIChatTemplateParams;
use goose_provider_types::conversation::message::{Message, MessageContent};
use goose_provider_types::errors::ProviderError;
use rmcp::model::CallToolRequestParams;
use serde_json::Value;
use std::borrow::Cow;
use uuid::Uuid;
use super::super::finalize_usage;
use super::super::thinking_output::ThinkingOutputFilter;
use super::inference_engine::{
context_cap, create_and_prefill_context, create_and_prefill_multimodal,
estimate_max_context_for_memory, generation_loop, validate_and_compute_context,
GenerationContext, TokenAction,
generation_loop, prepare_generation, GenerationContext, StopSuffixTrimmer, TokenAction,
};
pub(super) fn generate_with_native_tools(
ctx: &mut GenerationContext<'_>,
oai_messages_json: &Option<String>,
oai_messages_json: &str,
full_tools_json: Option<&str>,
compact_tools: Option<&str>,
) -> Result<(), ProviderError> {
let min_generation_headroom = 512;
let n_ctx_train = ctx.loaded.model.n_ctx_train() as usize;
let mmproj_overhead = if ctx.loaded.mtmd_ctx.is_some() {
ctx.settings.mmproj_size_bytes
} else {
0
};
let memory_max_ctx =
estimate_max_context_for_memory(&ctx.loaded.model, ctx.backend, mmproj_overhead);
let cap = context_cap(ctx.settings, ctx.context_limit, n_ctx_train, memory_max_ctx);
let token_budget = cap.saturating_sub(min_generation_headroom);
let apply_template = |tools: Option<&str>| {
if let Some(ref messages_json) = oai_messages_json {
let params = OpenAIChatTemplateParams {
messages_json: messages_json.as_str(),
tools_json: tools,
tool_choice: None,
json_schema: None,
grammar: None,
reasoning_format: if ctx.settings.enable_thinking {
Some("auto")
} else {
None
},
chat_template_kwargs: None,
add_generation_prompt: true,
use_jinja: true,
parallel_tool_calls: false,
enable_thinking: ctx.settings.enable_thinking,
add_bos: false,
add_eos: false,
parse_tool_calls: true,
};
ctx.loaded
.model
.apply_chat_template_oaicompat(&ctx.loaded.template, &params)
} else {
ctx.loaded.model.apply_chat_template_with_tools_oaicompat(
&ctx.loaded.template,
ctx.chat_messages,
tools,
None,
true,
)
}
};
let estimated_image_tokens = ctx.images.len() * ctx.settings.image_token_estimate;
let template_result = match apply_template(full_tools_json) {
Ok(r) => {
let token_count = ctx
.loaded
.model
.str_to_token(&r.prompt, AddBos::Never)
.map(|t| t.len())
.unwrap_or(0);
if token_count + estimated_image_tokens > token_budget {
apply_template(compact_tools).unwrap_or(r)
} else {
r
}
}
Err(_) => apply_template(compact_tools).map_err(|e| {
ProviderError::ExecutionError(format!("Failed to apply chat template: {}", e))
})?,
};
let _ = ctx.log.write(
&serde_json::json!({"applied_prompt": &template_result.prompt}),
None,
);
let (mut llama_ctx, prompt_token_count, effective_ctx) = if !ctx.images.is_empty() {
create_and_prefill_multimodal(
ctx.loaded,
ctx.backend,
&template_result.prompt,
ctx.images,
ctx.context_limit,
ctx.settings,
)?
} else {
let tokens = ctx
.loaded
.model
.str_to_token(&template_result.prompt, AddBos::Never)
.map_err(|e| ProviderError::ExecutionError(e.to_string()))?;
let (ptc, ectx) = validate_and_compute_context(
ctx.loaded,
ctx.backend,
tokens.len(),
ctx.context_limit,
ctx.settings,
)?;
let lctx =
create_and_prefill_context(ctx.loaded, ctx.backend, &tokens, ectx, ctx.settings)?;
(lctx, ptc, ectx)
};
let prepared = prepare_generation(ctx, oai_messages_json, full_tools_json, compact_tools)?;
let template_result = prepared.template_result;
let mut llama_ctx = prepared.llama_ctx;
let prompt_token_count = prepared.prompt_token_count;
let effective_ctx = prepared.effective_ctx;
let message_id = ctx.message_id;
let tx = ctx.tx;
let mut generated_text = String::new();
let mut stop_trimmer = StopSuffixTrimmer::new(&template_result.additional_stops);
let mut stop_string_emitted = false;
// Initialize streaming parser — handles thinking tokens, tool calls, etc.
let mut stream_parser = template_result.streaming_state_oaicompat().map_err(|e| {
@ -141,7 +45,10 @@ pub(super) fn generate_with_native_tools(
// Accumulate thinking/reasoning across the entire generation so we can
// attach it to the final tool-call message (mirroring what the OpenAI
// streaming path does). Streaming chunks are still sent for UI display.
let mut accumulated_thinking = String::new();
let mut output_filter = ThinkingOutputFilter::new(
ctx.settings.enable_thinking,
&template_result.generation_prompt,
);
let output_token_count = generation_loop(
&ctx.loaded.model,
@ -151,6 +58,7 @@ pub(super) fn generate_with_native_tools(
effective_ctx,
|piece| {
generated_text.push_str(piece);
let mut stop_seen = false;
// Feed the new piece to the streaming parser
match stream_parser.update(piece, true) {
@ -161,9 +69,10 @@ pub(super) fn generate_with_native_tools(
if let Some(reasoning) =
delta.get("reasoning_content").and_then(|v| v.as_str())
{
if !reasoning.is_empty() {
accumulated_thinking.push_str(reasoning);
let mut msg = Message::assistant().with_thinking(reasoning, "");
if let Some(thinking) =
output_filter.push_structured_reasoning(reasoning)
{
let mut msg = Message::assistant().with_thinking(thinking, "");
msg.id = Some(message_id.to_string());
if tx.blocking_send(Ok((Some(msg), None))).is_err() {
return Ok(TokenAction::Stop);
@ -173,10 +82,15 @@ pub(super) fn generate_with_native_tools(
// Stream content text to the UI
if let Some(content) = delta.get("content").and_then(|v| v.as_str()) {
if !content.is_empty() {
let mut msg = Message::assistant().with_text(content);
msg.id = Some(message_id.to_string());
if tx.blocking_send(Ok((Some(msg), None))).is_err() {
return Ok(TokenAction::Stop);
let filtered = output_filter.push_text(content);
let (content, seen) = stop_trimmer.push(&filtered.content);
stop_seen |= seen;
if !content.is_empty() {
let mut msg = Message::assistant().with_text(content);
msg.id = Some(message_id.to_string());
if tx.blocking_send(Ok((Some(msg), None))).is_err() {
return Ok(TokenAction::Stop);
}
}
}
}
@ -193,19 +107,26 @@ pub(super) fn generate_with_native_tools(
}
Err(e) => {
tracing::warn!("Streaming parser error: {}", e);
let mut msg = Message::assistant().with_text(piece);
msg.id = Some(message_id.to_string());
if tx.blocking_send(Ok((Some(msg), None))).is_err() {
return Ok(TokenAction::Stop);
let filtered = output_filter.push_text(piece);
let (content, seen) = stop_trimmer.push(&filtered.content);
stop_seen |= seen;
if !content.is_empty() {
let mut msg = Message::assistant().with_text(content);
msg.id = Some(message_id.to_string());
if tx.blocking_send(Ok((Some(msg), None))).is_err() {
return Ok(TokenAction::Stop);
}
}
}
}
let should_stop = template_result
.additional_stops
.iter()
.any(|stop| generated_text.ends_with(stop));
let should_stop = stop_seen
|| template_result
.additional_stops
.iter()
.any(|stop| generated_text.ends_with(stop));
if should_stop {
stop_string_emitted = true;
Ok(TokenAction::Stop)
} else {
Ok(TokenAction::Continue)
@ -218,18 +139,22 @@ pub(super) fn generate_with_native_tools(
for delta_json in final_deltas {
if let Ok(delta) = serde_json::from_str::<Value>(&delta_json) {
if let Some(reasoning) = delta.get("reasoning_content").and_then(|v| v.as_str()) {
if !reasoning.is_empty() {
accumulated_thinking.push_str(reasoning);
let mut msg = Message::assistant().with_thinking(reasoning, "");
if let Some(thinking) = output_filter.push_structured_reasoning(reasoning) {
let mut msg = Message::assistant().with_thinking(thinking, "");
msg.id = Some(message_id.to_string());
let _ = tx.blocking_send(Ok((Some(msg), None)));
}
}
if let Some(content) = delta.get("content").and_then(|v| v.as_str()) {
if !content.is_empty() {
let mut msg = Message::assistant().with_text(content);
msg.id = Some(message_id.to_string());
let _ = tx.blocking_send(Ok((Some(msg), None)));
let filtered = output_filter.push_text(content);
let (content, stop_seen) = stop_trimmer.push(&filtered.content);
stop_string_emitted |= stop_seen;
if !content.is_empty() {
let mut msg = Message::assistant().with_text(content);
msg.id = Some(message_id.to_string());
let _ = tx.blocking_send(Ok((Some(msg), None)));
}
}
}
if let Some(tool_calls) = delta.get("tool_calls").and_then(|v| v.as_array()) {
@ -241,6 +166,28 @@ pub(super) fn generate_with_native_tools(
}
}
let filtered = output_filter.finish();
if !filtered.thinking.is_empty() {
let mut msg = Message::assistant().with_thinking(&filtered.thinking, "");
msg.id = Some(message_id.to_string());
let _ = tx.blocking_send(Ok((Some(msg), None)));
}
let content = if stop_string_emitted {
String::new()
} else {
let (content, stop_seen) = stop_trimmer.push(&filtered.content);
let mut content = content;
if !stop_seen {
content.push_str(&stop_trimmer.finish());
}
content
};
if !content.is_empty() {
let mut msg = Message::assistant().with_text(content);
msg.id = Some(message_id.to_string());
let _ = tx.blocking_send(Ok((Some(msg), None)));
}
// Build a single message combining thinking + all tool calls, mirroring
// the structure produced by the OpenAI streaming path. The agent relies
// on this combined message to:
@ -250,8 +197,11 @@ pub(super) fn generate_with_native_tools(
let tool_call_contents = extract_oai_tool_call_contents(&accumulated_tool_calls);
if !tool_call_contents.is_empty() {
let mut contents: Vec<MessageContent> = Vec::new();
if !accumulated_thinking.is_empty() {
contents.push(MessageContent::thinking(&accumulated_thinking, ""));
if !output_filter.accumulated_thinking().is_empty() {
contents.push(MessageContent::thinking(
output_filter.accumulated_thinking(),
"",
));
}
contents.extend(tool_call_contents);
let mut msg = Message::new(

Some files were not shown because too many files have changed in this diff Show more