Document process monitoring handoff

Add ACP discovery and configuration support
This commit is contained in:
Ryan Ward
2026-07-30 11:53:33 -05:00
parent ad24374f6d
commit 1a0aac51b6
18 changed files with 1280 additions and 664 deletions
+218
View File
@@ -0,0 +1,218 @@
# ACP Model, Mode, Registry, and Credential Implementation Plan
## Goal
Complete Galaxy's ACP integration so that ACP agents can be discovered, configured, selected through the normal model selector, launched with the selected model/configuration, and persisted safely across sessions. Keep all ACP metadata, settings, persistence, selector behavior, runtime protocol handling, and settings UI synchronized without duplicate representations or dead code.
## Constraints and invariants
- ACP session configuration is represented by the protocol's `configOptions`, not a hard-coded model/mode matrix.
- Prefer `configOptions` over the legacy `modes` API.
- Preserve agent-provided option and value ordering.
- Treat option values as opaque IDs; never parse display labels to reconstruct values.
- Omitted ACP capabilities mean unsupported.
- Only send capability-gated request fields when the agent advertises support.
- Registry metadata describes agents and distributions; it does not provide live model/mode availability.
- Live model/config discovery comes from the running ACP agent.
- Keep non-secret ACP metadata in settings/persistence.
- Keep credentials in secure storage/Keychain; do not put secrets in settings.toml or ACP command arguments.
- Preserve support for custom ACP agents not present in the registry.
- Avoid duplicate selector/model representations; use shared normalized ACP types.
- Do not regress existing Bedrock, OpenAI/LiteLLM, MCP, authentication, or provider behavior.
## Shared data model
Create or consolidate one normalized ACP data model shared between the ACP crate, settings, persistence, model selector, and settings UI.
Required concepts:
- `AcpAgentInfo`
- registry ID
- display name/title/name
- description
- version
- repository/website
- authors/license
- icon URL
- distribution metadata
- `AcpAgentCapabilities`
- load session
- resume
- close/delete session
- additional directories
- prompt image/audio/embedded context
- MCP HTTP/SSE
- boolean config options
- `AcpConfigOption`
- option ID
- name
- description
- category
- type
- current value
- ordered values
- `AcpConfigValue`
- opaque value ID/value
- display name
- description
- `AcpModelSelection`
- agent ID
- complete selected option-value map
- stable selector ID
- display labels
Use serde-compatible forms for settings and persistence. Keep protocol-native conversion code in the ACP crate or one app adapter module, not duplicated in multiple consumers.
## Task list
### Phase 1: Establish clean shared types
- [ ] Inspect current ACP schema exports and app settings/persistence types.
- [ ] Replace ad hoc `serde_json::Value` use where practical with a shared serializable opaque ACP config value representation.
- [ ] Add explicit conversion functions between generated ACP schema types and Galaxy settings/persistence types.
- [ ] Add stable equality/hash/identity helpers for config selections.
- [ ] Add unit tests for option/value conversion, unknown categories, unknown option types, boolean values, and ordering.
- [ ] Ensure `AcpConversationData::for_fork` preserves selections while clearing session IDs.
### Phase 2: ACP initialization and discovery
- [ ] Extend ACP initialization client capabilities to advertise supported boolean config options.
- [ ] Capture `agentInfo` from `initialize`.
- [ ] Capture all relevant initialization capabilities.
- [ ] Add an explicit discovery API on `AcpSessionManager` or a dedicated discovery result path.
- [ ] Create a temporary discovery session using the configured working directory and appropriate MCP servers.
- [ ] Read the complete `configOptions` response from `session/new`.
- [ ] Preserve agent option/value ordering.
- [ ] Support discovery for custom executables as well as built-in presets.
- [ ] Close/delete the temporary discovery session when the advertised capability allows it.
- [ ] Make discovery failures non-destructive: retain last known cache and expose an actionable error.
- [ ] Add discovery timeout and cancellation handling.
- [ ] Add tests using fake ACP agents for successful discovery, authentication failure, missing options, malformed options, and timeout.
### Phase 3: Runtime config application and updates
- [ ] Apply selected values after `session/new` and before the first prompt.
- [ ] Capture the complete response from `session/set_config_option`.
- [ ] Replace the current complete config state after every successful set operation.
- [ ] Convert `config_option_update` notifications into app-visible events.
- [ ] Reconcile dependent options when changing a model changes modes/thought levels.
- [ ] Reject or safely drop stale values that are no longer advertised.
- [ ] Ensure restored sessions reconcile persisted selections with the agent's current config state.
- [ ] Track current config values in runtime session metadata.
- [ ] Apply capability checks for load/resume/close/delete/additional directories/MCP transports.
- [ ] Add tests for setting options, dependent option changes, rejected values, and runtime notifications.
### Phase 4: Settings.toml persistence and synchronization
- [ ] Finalize the `ai.acp.agents` settings schema.
- [ ] Persist registry metadata and runtime discovery metadata without credentials.
- [ ] Persist capabilities and complete config options atomically.
- [ ] Store discovery timestamp and source/version information.
- [ ] Invalidate or refresh discovery when launch fingerprint, executable, arguments, preset version, or agent version changes.
- [ ] Preserve custom launch configuration while refreshing registry metadata.
- [ ] React to ACP settings changes and refresh the model list.
- [ ] Handle malformed/stale settings gracefully.
- [ ] Add settings schema validation and round-trip tests.
- [ ] Add a documented example to the repository's settings documentation or sample `settings.toml` if one exists.
### Phase 5: ACP Registry integration
- [ ] Add a registry client for `https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json`.
- [ ] Parse the registry version and agent entries.
- [ ] Support npm/npx, uvx, and platform-specific binary distributions.
- [ ] Select the correct operating system and architecture distribution.
- [ ] Preserve command, args, environment, archive, checksum, icon, and version metadata.
- [ ] Validate URLs, commands, arguments, environment names, and checksums before use.
- [ ] Cache the registry with a refresh timestamp and last-known-good fallback.
- [ ] Never auto-install or execute an agent without explicit user action.
- [ ] Preserve custom agents outside the registry.
- [ ] Add registry parsing/platform-selection/security tests.
### Phase 6: Model selector integration
- [ ] Add ACP choices through the existing `LLMPreferences`/model selector path.
- [ ] Use structured selection data instead of parsing encoded display IDs.
- [ ] Show ACP choices under an ACP agent grouping.
- [ ] Render model labels such as `GPT 5.4 Sol (Ultra)` while preserving structured values.
- [ ] Use `model` as the primary selector dimension.
- [ ] Use `mode` and `thought_level` as secondary labels or grouped choices.
- [ ] Keep `model_config` and advanced/unknown options in an ACP configuration panel rather than exploding the model list.
- [ ] Avoid generating invalid Cartesian combinations for dependent options.
- [ ] Refresh selector choices after discovery or config updates.
- [ ] Ensure the selected row propagates into `AcpConversationData.config_values`.
- [ ] Ensure model metadata, transcript metadata, usage metadata, and restored conversations use the selected ACP identity.
- [ ] Add selector and selection propagation tests.
### Phase 7: Agent screen/settings UI
- [ ] Extend the ACP settings widget with registry-backed agent selection.
- [ ] Show agent icon, display name, description, version, and installation status.
- [ ] Add refresh registry action.
- [ ] Add discover/refresh capabilities action.
- [ ] Show discovery status, timestamp, and actionable errors.
- [ ] Show discovered models/modes and option descriptions.
- [ ] Show advanced config options without duplicating selector logic.
- [ ] Add install/configure action only with explicit user confirmation.
- [ ] Support custom command/args fields and preserve them when switching registry entries.
- [ ] Use existing button themes and UI components.
- [ ] Add UI tests or view-model tests for loading, error, refresh, and selection states.
### Phase 8: Credentials and secure storage
- [ ] Leave global Keychain registration intact.
- [ ] Audit ACP/OpenAI/LiteLLM credential paths for secrets in settings.toml or command arguments.
- [ ] Store Galaxy-managed ACP/OpenAI/LiteLLM API keys through the existing secure-storage service.
- [ ] Keep only non-secret references/configuration in settings.toml.
- [ ] Add migration from existing plaintext settings keys to secure storage.
- [ ] Decide and document behavior when secure storage is unavailable.
- [ ] Do not copy or manage credentials owned by ACP agents themselves.
- [ ] Add tests for secure-storage read/write/migration/error behavior.
### Phase 9: Backend/provider/translators
- [ ] Ensure ACP selected configuration flows through request creation, response streaming, persistence, restoration, and fork paths.
- [ ] Ensure ACP translator model IDs use complete structured selections.
- [ ] Ensure OpenAI/LiteLLM provider routing remains unchanged.
- [ ] Ensure Bedrock provider routing remains unchanged.
- [ ] Ensure no ACP-only assumptions leak into provider translators.
- [ ] Add integration coverage for OpenCode over LiteLLM.
- [ ] Verify cancellation, steering, tool calls, usage, and session restore with selected config values.
### Phase 10: Validation and cleanup
- [ ] Search for duplicate ACP model/config representations and consolidate them.
- [ ] Remove temporary or dead ACP helper code.
- [ ] Add exhaustive enum matching where applicable.
- [ ] Run formatting.
- [ ] Run targeted ACP/persistence/settings/model tests.
- [ ] Run workspace checks.
- [ ] Run nextest.
- [ ] Run clippy with warnings denied.
- [ ] Review Cargo.lock changes from `cargo update`.
- [ ] Review security implications of registry execution and credential migration.
- [ ] Confirm settings UI, selector, runtime, persistence, registry, and secure storage are synchronized.
## Required validation commands
```bash
./script/format
cargo check --workspace
cargo nextest run --no-fail-fast --workspace --exclude command-signatures-v2
cargo clippy --workspace --all-targets --all-features --tests -- -D warnings
cargo test --doc
```
## Completion criteria
The work is complete only when:
1. A configured ACP agent can be discovered and its live models/config options are cached.
2. The normal model selector displays valid ACP model/config choices.
3. Selecting a choice persists the complete structured configuration.
4. A new ACP session receives the selected values before prompting.
5. Dynamic config updates refresh the selector/settings cache.
6. Restored and forked conversations preserve correct selection behavior.
7. Registry metadata can configure/install known agents without excluding custom agents.
8. Credentials use secure storage and are not written to settings.toml.
9. Existing Bedrock/OpenAI/LiteLLM/MCP/auth behavior remains intact.
10. Targeted tests, workspace checks, formatting, nextest, and clippy pass.
+161
View File
@@ -0,0 +1,161 @@
# ACP Implementation Task List
Status key: `[ ]` not started · `[~]` in progress · `[x]` complete · `[!]` blocked
Execution mode: autonomous batch implementation. Continue through all phases without pausing for confirmation; stop only for a genuine external blocker or after final validation.
## Phase 1 — Shared types
- [~] Define shared normalized ACP agent metadata.
- [~] Define shared ACP capability representation.
- [x] Define shared ACP config option/value representation.
- [~] Define structured `AcpModelSelection`.
- [x] Add protocol-to-Galaxy conversion helpers.
- [ ] Add selection identity/equality helpers.
- [ ] Replace avoidable ad hoc JSON representations.
- [ ] Add conversion and ordering tests.
- [x] Persist ACP selections in `AcpConversationData`.
- [x] Preserve selections across forks while clearing session IDs.
## Phase 2 — ACP discovery
- [ ] Advertise supported boolean config options during initialization.
- [x] Capture `initialize.agentInfo`.
- [x] Capture initialization capabilities.
- [x] Add manager-level metadata accessors.
- [x] Add manager-level discovery API.
- [x] Create temporary discovery sessions.
- [x] Read `configOptions` from `session/new`.
- [x] Preserve option/value ordering.
- [x] Remove placeholder synchronous config accessor once async discovery is available.
- [ ] Support custom executable discovery.
- [ ] Close/delete temporary sessions when supported.
- [ ] Preserve last-known-good cache on failure.
- [ ] Add timeout/cancellation handling.
- [ ] Add fake-agent discovery tests.
## Phase 3 — Runtime config handling
- [x] Carry selected config values in `AcpTurnRequest`.
- [x] Apply selected values after `session/new`.
- [x] Export `SessionConfigOptionValue` from `galaxy_acp`.
- [~] Capture `session/set_config_option` responses.
- [ ] Replace complete current config state after setting an option.
- [x] Add async runtime result channels for discovery/config state.
- [x] Emit `ConfigOptions` for `config_option_update` notifications.
- [ ] Reconcile dependent options.
- [ ] Reject/drop stale selections safely.
- [ ] Reconcile restored sessions with current agent config.
- [x] Track latest config options in session metadata.
- [ ] Enforce all capability-gated behavior.
- [ ] Add runtime config tests.
## Phase 4 — Settings.toml
- [x] Add `ai.acp.agents` setting.
- [x] Store agent metadata/config options in settings.
- [~] Store capabilities in normalized form.
- [ ] Store discovery timestamps/source/version.
- [~] Wire discovery results into `AISettings.acp_agents`.
- [ ] Atomically persist discovery results.
- [ ] Invalidate cache on launch/version changes.
- [ ] Preserve custom launch settings during refresh.
- [ ] Refresh model preferences when settings change.
- [ ] Handle malformed/stale cache values.
- [ ] Add settings schema/round-trip tests.
- [ ] Add documented settings example.
## Phase 5 — ACP Registry
- [ ] Implement registry client.
- [ ] Parse registry version and agents.
- [ ] Parse npm/npx distributions.
- [ ] Parse uvx distributions.
- [ ] Parse platform binary distributions.
- [ ] Select OS/architecture distribution.
- [ ] Preserve command/args/env/archive/checksum/icon/version metadata.
- [ ] Validate registry commands, URLs, environment names, and checksums.
- [ ] Cache last-known-good registry data.
- [ ] Add explicit user-confirmed installation flow.
- [ ] Preserve custom agents.
- [ ] Add registry tests.
## Phase 6 — Model selector
- [~] Inject ACP choices through `LLMPreferences`.
- [ ] Use structured selection data instead of parsing IDs.
- [ ] Group entries by ACP agent.
- [x] Display model/mode labels such as `GPT 5.4 Sol (Ultra)`.
- [ ] Use `model` as primary selector dimension.
- [ ] Use `mode`/`thought_level` as secondary display/configuration.
- [ ] Keep `model_config`/unknown options in advanced configuration UI.
- [ ] Avoid invalid dependent combinations.
- [ ] Refresh choices after discovery/config updates.
- [ ] Propagate selected values into conversation persistence.
- [ ] Use selected identity in transcripts/usage/restoration.
- [ ] Add selector propagation tests.
## Phase 7 — Agent Settings UI
- [ ] Add registry-backed agent list.
- [ ] Show icon/name/description/version/install status.
- [ ] Add registry refresh action.
- [ ] Add ACP capability discovery action.
- [ ] Show discovery status/timestamp/errors.
- [ ] Show discovered models/modes/options.
- [ ] Show advanced options without duplicating selector logic.
- [ ] Add explicit install/configure confirmation.
- [ ] Preserve custom command/args.
- [ ] Use shared UI themes/components.
- [ ] Add settings UI tests.
## Phase 8 — Credentials
- [x] Keep global Keychain integration intact.
- [ ] Audit ACP/OpenAI/LiteLLM plaintext credential paths.
- [ ] Store Galaxy-managed ACP/provider credentials in secure storage.
- [ ] Keep only non-secret references in settings.toml.
- [ ] Migrate existing plaintext credentials.
- [ ] Define unavailable-secure-storage behavior.
- [ ] Keep ACP-owned credentials under ACP agent control.
- [ ] Add credential migration/security tests.
## Phase 9 — Backend/translators
- [~] Verify selection flow from UI to request creation.
- [x] Verify persistence/restoration/fork flow.
- [~] Use complete structured ACP identity in translators.
- [ ] Preserve OpenAI/LiteLLM routing.
- [ ] Preserve Bedrock routing.
- [ ] Keep ACP assumptions isolated from provider translators.
- [ ] Add OpenCode + LiteLLM integration coverage.
- [ ] Verify cancellation/steering/tools/usage/session restore.
## Phase 10 — Cleanup and validation
- [ ] Remove duplicate ACP representations.
- [ ] Remove dead/temporary ACP helpers.
- [ ] Review exhaustive matches.
- [ ] Run `./script/format`.
- [ ] Run targeted ACP/persistence/settings/model tests.
- [ ] Run `cargo check --workspace`.
- [ ] Run nextest.
- [ ] Run clippy with warnings denied.
- [ ] Run doc tests.
- [ ] Review Cargo.lock changes.
- [ ] Review registry execution security.
- [ ] Review credential migration security.
- [ ] Confirm Settings, Agent screen, selector, runtime, persistence, registry, and secure storage are synchronized.
## Completion gates
- [ ] Live ACP discovery works for OpenCode and Codex.
- [ ] Discovered options persist in `ai.acp.agents`.
- [ ] Selector shows valid ACP model/config choices.
- [ ] Selected structured values reach `session/set_config_option` before prompting.
- [ ] Dynamic agent config updates refresh Galaxy state.
- [ ] Restored/forked conversations retain correct ACP selections.
- [ ] Registry metadata supports known and custom agents.
- [ ] Credentials are not written to settings.toml.
- [ ] Existing Bedrock/OpenAI/LiteLLM/MCP/auth behavior is unchanged.
- [ ] All required validation commands pass.
+206 -174
View File
@@ -1,232 +1,264 @@
---
name: bring-warp-feature-over
description: Identify recent features merged into upstream Warp, assess eligibility for the Galaxy fork, and migrate selected features with Bedrock API adaptations. Use when syncing new Warp functionality into Galaxy.
description: Find and selectively port explicitly requested upstream Warp pull requests into Galaxy. Accepts PR/MR numbers or a user-provided feature description, confirms the selected PRs before editing, filters out Warp-only service code, preserves Galaxy's Bedrock/OpenAI/ACP provider architecture and branding, and adapts eligible local changes.
---
# bring-warp-feature-over
# Bring a Specific Warp Feature into Galaxy
Fetches recent upstream Warp changes, filters for eligible features, lets the user pick which to migrate, plans the work, and spawns agents to implement each migration.
Use this skill to identify and port selected upstream Warp functionality into Galaxy without performing a broad upstream synchronization.
## Overview
## Required scope
Galaxy is a fork of Warp that uses AWS Bedrock instead of Warp's proprietary AI APIs. When upstream Warp ships new features, this skill identifies which ones can be brought over, adapts API calls to Bedrock, and replaces Warp-specific branding where needed.
The user must provide either:
## Workflow
1. One or more upstream Warp pull request numbers, or
2. A description of the feature they want to find upstream.
### 1. Fetch recent upstream changes
Examples:
Use the GitHub API to pull merged commits from `warpdotdev/warp` on the default branch. Group by PR (commits reference `#<number>`).
- `Bring over Warp PR #12345`
- `Port PRs 12345 and 12389`
- `Find the Warp PR that added terminal search highlighting`
- `Bring over the recent worktree picker improvements`
If the user provides a description, search for candidate PRs and ask the user to confirm the matching PR number(s) before making code changes. Do not scan and port arbitrary recent history.
## Non-negotiable Galaxy boundaries
Galaxy-specific architecture always wins. Never replace or bypass:
- Bedrock provider code in `app/src/ai/bedrock/`
- OpenAI/LiteLLM provider code in `app/src/ai/openai/`
- ACP provider/runtime code in `app/src/ai/acp/` and the related ACP crate(s)
- Provider dispatch and response streaming in `app/src/ai/provider/` and `app/src/ai/blocklist/controller/response_stream.rs`
- Galaxy branding, package names, channels, settings, deployment, and installer behavior
Do not import or preserve new upstream code that depends on:
- Warp's proprietary AI API or hosted agent backend
- Warp authentication, billing, subscriptions, teams, or cloud-only flows
- Warp telemetry/analytics that phone home to Warp
- Warp server GraphQL/API endpoints unless explicitly adapted to an existing Galaxy service
- Warp-specific deployment or branding
- Oz/hosted orchestration that Galaxy cannot run locally
## Phase 0: Inspect the working tree
Before making changes:
```bash
# Fetch commits from the last N days (default 14)
curl -s "https://api.github.com/repos/warpdotdev/warp/commits?per_page=100&since=$(date -u -v-14d +%Y-%m-%dT00:00:00Z)" \
| python3 -c "
import json, sys
data = json.load(sys.stdin)
for c in data:
sha = c['sha'][:8]
msg = c['commit']['message'].split('\n')[0]
date = c['commit']['author']['date'][:10]
print(f'{date} {sha} {msg}')
"
git --no-optional-locks status --short --branch
```
If the user specifies a date range or number of days, adjust the `since` parameter. For longer lookups, paginate with `&page=2`, etc.
Do not overwrite unrelated user work. If the working tree is dirty, keep existing changes intact and use a separate branch where possible.
## Phase 1: Resolve the requested scope
### If PR/MR numbers were provided
Use the supplied numbers directly. Do not search unrelated upstream history.
### If a feature description was provided
Search upstream GitHub for matching Warp PRs. Prefer the GitHub search API, using a concise query derived from the users description:
To get more detail on a specific PR:
```bash
curl -s "https://api.github.com/repos/warpdotdev/warp/pulls/<PR_NUMBER>" | python3 -c "
import json, sys
pr = json.load(sys.stdin)
print(pr.get('title'))
print(pr.get('body','')[:2000])
"
curl -sS --get 'https://api.github.com/search/issues' \
--data-urlencode 'q=<keywords> repo:warpdotdev/warp is:pr' \
--data-urlencode 'per_page=10'
```
To see the files changed in a PR:
If authentication or API rate limits prevent the search, use the public GitHub web search URL or fetch recent commit/PR metadata as a fallback. Keep the search bounded by the users description; do not enumerate the entire repository history.
For each candidate, collect:
- PR number and title
- State and merge status
- Updated/merged date
- Body excerpt
- Labels, when available
- Changed-file summary
Inspect candidate files before presenting them:
```bash
curl -s "https://api.github.com/repos/warpdotdev/warp/pulls/<PR_NUMBER>/files" | python3 -c "
import json, sys
files = json.load(sys.stdin)
for f in files:
print(f['status'], f['filename'])
"
curl -sS "https://api.github.com/repos/warpdotdev/warp/pulls/<PR>"
curl -sS "https://api.github.com/repos/warpdotdev/warp/pulls/<PR>/files?per_page=100"
```
### 2. Assess eligibility
Present a short ranked list with the reason each candidate matches and its likely adaptation cost. Then ask for confirmation, for example:
For each feature/PR, determine eligibility. A feature is **ineligible** if:
```text
I found these likely matches:
- It depends on Warp's server APIs that we don't have access to (e.g. `warp-server` endpoints, GraphQL mutations for Warp Cloud services)
- It's specific to "Warp Drive" syncing infrastructure (unless it can be adapted to "Galaxy Drive")
- It references "Oz" orchestration that relies on Warp's hosted agent backend
- It requires Warp's proprietary AI proxy and cannot be redirected to Bedrock
- It's purely a Warp billing, subscription, or team management feature
- It touches WASM-only paths that Galaxy doesn't ship
1. #12345 — Improve terminal search highlighting
Matches the requested behavior; mostly local terminal/UI code. Low adaptation cost.
A feature **is eligible** if:
2. #12389 — Add cloud search synchronization
Related wording, but depends on Warp cloud APIs. Likely ineligible.
- It's a client-side UX improvement (terminal, editor, completions, themes, settings)
- It's an AI feature that calls a model API we can route through Bedrock (Claude, etc.)
- It's a local-only feature (git integration, file system, SSH, etc.)
- It's a bug fix applicable to shared code paths
- It touches "Warp Drive" but can be rebranded to "Galaxy Drive"
When assessing, also note the **adaptation cost**:
- **Low**: Drop-in (UI fix, terminal behavior, keybindings)
- **Medium**: Needs Bedrock API mapping or minor branding changes
- **High**: Significant refactoring of server-dependent code to work with Bedrock
### 3. Present feature checklist to user
After assessment, present the eligible features to the user using `AskUserQuestion` with `multiSelect: true`. Group by adaptation cost. Include the PR title and a one-line summary of what it does.
Example:
```
Which features would you like to bring over?
Low effort:
- [ ] #10958 - Make worktree menu paths readable for long entries
- [ ] #11099 - Clip terminal view column to prevent split-pane footer overflow
Medium effort:
- [ ] #11049 - Add sleep auto handoff to cloud (needs Bedrock adaptation)
High effort:
- [ ] #10857 - Add orchestration create environment modal (heavy server dependency)
Which PR(s) should I port? Reply with the number(s), or say “none”.
```
### 4. Research selected features
Do not create a branch, apply patches, or edit source files until the user confirms the selected PR number(s). Research and recommendation are allowed before confirmation.
For each selected feature, perform detailed research:
If the search returns no confident match, say so and ask for a PR link, more keywords, or clarification. Do not guess.
1. **Read the PR diff** — use the GitHub API to understand what changed:
## Phase 2: Fetch and inspect only the confirmed PRs
After the user confirms PR numbers, fetch upstream refs if needed:
```bash
git fetch warp master
```
For each confirmed PR, inspect:
- PR title, state, merge commit, and description
- Files changed and additions/deletions
- New dependencies, feature flags, migrations, or generated files
- Whether the change is local-only or depends on Warp services
- Whether Galaxy has renamed or diverged from the affected paths
For local context:
```bash
git log --oneline --all -- <path>
git diff HEAD...warp/master -- <path>
```
## Phase 3: Eligibility decision
Classify each confirmed PR as one of:
### Eligible: direct port
Usually local terminal, editor, UI, completion, parser, filesystem, SSH, theme, or platform bug fixes with limited dependencies.
### Eligible: adapted port
Useful functionality that touches Galaxy provider or branding boundaries but can be redirected safely to Bedrock, OpenAI/LiteLLM, or ACP. Document the adaptation before editing.
### Ineligible
Reject the PR, or only extract isolated local hunks, when it fundamentally depends on Warp-only services such as hosted AI, Warp auth, billing, telemetry, server GraphQL, Warp Drive infrastructure, or Oz orchestration.
When a PR mixes eligible and ineligible changes, do not cherry-pick the whole PR. Port only the eligible files or hunks manually.
Summarize the decision before implementation. If the user asked to implement and the PR is clearly eligible, proceed after the PR confirmation. If the PR has meaningful adaptation risk, explain the risk and ask before making substantial changes.
## Phase 4: Create a focused working branch
Before source changes:
```bash
git switch -c port-warp-pr-<PR>
```
For multiple PRs, use a descriptive branch containing all numbers, or apply them sequentially on one focused branch.
Do not commit unless the user explicitly requests it.
## Phase 5: Apply the smallest safe change
Prefer these strategies in order:
1. Cherry-pick without committing only when the PR is narrowly scoped and service-free:
```bash
curl -s "https://api.github.com/repos/warpdotdev/warp/pulls/<PR>/files?per_page=100"
git cherry-pick -n <merge-commit-or-commit>
```
2. Apply only eligible paths:
```bash
git diff <base> <commit> -- <eligible-paths> | git apply --3way
```
3. Manually port the relevant hunks when Galaxy renamed paths, has diverged, or the PR mixes service and local changes.
2. **Map to local files** — identify corresponding files in our Galaxy fork. Check if the files exist and what state they're in.
Do not use a blanket `git merge warp/master` for this skill.
3. **Identify Bedrock adaptations** — if the feature makes AI/model calls, document:
- What model is being called and how
- What the equivalent Bedrock invocation looks like
- What request/response transformations are needed
Preserve Galaxy naming and adapt upstream imports where necessary:
4. **Identify branding changes** — flag any references to:
- "Warp Drive" → "Galaxy Drive"
- "Oz" → remove or replace
- Warp-specific UI copy that needs updating
- `warpui` → `galaxyui`
- `warpui_core` → `galaxyui_core`
- `warp_core` → `galaxy_core`
- `warp_terminal` → `galaxy_terminal`
- `warp_editor` → `galaxy_editor`
5. **Document dependencies** — note any new crates, feature flags, or config changes needed.
Do not perform repository-wide renames as part of a feature port.
### 5. Write migration plans
## Provider-specific adaptation rules
For each selected feature, create a plan file at:
```
plans/warp-migrations/<PR_NUMBER>-<short-slug>.md
```
### Bedrock and OpenAI/LiteLLM
Each plan should contain:
A PR that adds AI behavior must use Galaxys provider dispatch. Adapt request/response types to the existing provider interfaces rather than importing Warps AI client or endpoint.
```markdown
# Migration: <PR Title>
### ACP
**Source PR**: warpdotdev/warp#<number>
**Adaptation Cost**: Low | Medium | High
**Date Assessed**: <today>
ACP changes are allowed only when they preserve Galaxys ACP runtime, transport, launch, and model-selection architecture. Do not replace ACP with Warps hosted agent service or introduce a Warp-specific backend. Inspect relevant files under `app/src/ai/acp/` and the ACP crate before applying upstream changes.
## Summary
<What the feature does, 2-3 sentences>
### Shared agent/blocklist code
## Files Changed (upstream)
<List of files from the PR>
Treat `app/src/ai/agent/` and `app/src/ai/blocklist/` as manual-merge areas. Keep Galaxys provider selection, tool handling, persistence, and ACP integration intact even when adopting local UX or controller improvements.
## Local File Mapping
<Corresponding Galaxy files, noting any that don't exist yet>
## API and service leak checks
## Required Adaptations
- <Bedrock changes if any>
- <Branding changes if any>
- <New dependencies if any>
## Implementation Steps
1. <Step 1>
2. <Step 2>
...
## Testing Notes
<How to verify this works in Galaxy>
```
### 6. Spawn implementation agents
For each migration plan, spawn an agent using the `Agent` tool to make the code changes. Key rules for spawning:
- **Agents only write code** — they do NOT run `cargo check`, `cargo build`, or `cargo clippy`
- **Spawn agents in parallel** for independent features (no shared file conflicts)
- **Spawn sequentially** if two features touch the same files
- Each agent's prompt must include:
- The full migration plan content
- The specific files to modify and what changes to make
- Instructions to NOT run cargo commands
- Instructions to report back what files were changed
Example agent prompt structure:
```
You are implementing a Warp feature migration into the Galaxy fork.
Migration plan:
<paste plan content>
Instructions:
- Make ONLY the code changes described in the plan
- Do NOT run cargo check, cargo build, cargo clippy, or any compilation commands
- Adapt any Warp API calls to use Bedrock (see plan for specifics)
- Replace "Warp Drive" with "Galaxy Drive" where applicable
- Remove or skip any "Oz" references
- Report back: list all files you modified and a brief summary of changes
```
### 7. Verify builds (parent agent only)
After all implementation agents complete, the parent agent (you) runs:
Review new code before accepting it:
```bash
cargo fmt
cargo clippy --workspace --all-targets --all-features --tests -- -D warnings
grep -RInE 'api\.warp\.dev|warp\.dev/v1|WarpAIService|WarpAiClient|WARP_API_KEY|WARP_AUTH_TOKEN' app/src crates --include='*.rs'
```
If there are errors, fix them directly or re-delegate targeted fixes to agents.
New hits introduced by the port must be removed or adapted. Do not mechanically remove existing compatibility names without checking their purpose.
### 8. Instruct user to test
## Dependencies and generated files
After a clean build, present the user with testing instructions:
For each new dependency:
- List each migrated feature and how to exercise it
- Note any features that need specific configuration or feature flags enabled
- Ask the user to report back any issues they find
- Remind them to test with `cargo run` and verify the features work end-to-end
1. Check whether Galaxy already has an equivalent.
2. Add the smallest local/client-side dependency needed.
3. Reject dependencies that exist only for Warp services, hosted AI, auth, telemetry, or billing.
4. Regenerate code only when the PR genuinely changes a Galaxy-supported schema or generated source.
## Branding Reference
## Validation
| Upstream (Warp) | Galaxy equivalent |
|-----------------------|-----------------------|
| Warp Drive | Galaxy Drive |
| Oz / Orchestrator | Remove or skip |
| Warp AI / Warp Agent | Galaxy AI / Agent |
| warp-server endpoints | Skip (ineligible) |
Run focused checks based on changed files:
## Bedrock Adaptation Patterns
```bash
git diff --check
cargo fmt --all -- --check
```
When adapting AI features from Warp's proxy to Bedrock:
Typical targeted checks include:
- Warp's AI calls typically go through their proxy server — Galaxy calls Bedrock directly
- Look for the existing Bedrock integration patterns in `app/src/ai/` for how Galaxy makes model calls
- Ensure streaming responses are handled correctly (Bedrock uses different event formats)
- Check `WARP.md` "Bedrock Diagnostics" section for debugging tools
```bash
cargo check -p galaxy_terminal
cargo check -p galaxyui
cargo check -p galaxy_editor
cargo check -p galaxy
```
## Related Skills
Also check the ACP crate or affected package when ACP files change. Use the actual package names from the relevant `Cargo.toml` files.
- `fix-errors` — for resolving build failures after migration
- `add-feature-flag` — if the migrated feature needs gating
- `implement-specs` — for larger features that need full specs
For a release-ready port, run:
```bash
./script/format
cargo clippy --workspace --all-targets --all-features --tests -- -D warnings
cargo build
```
Fix errors caused by the port, but do not pull in excluded Warp service code to make the build pass.
## Output report
Report:
- Search description, candidate PRs, and the users confirmed selection
- Eligibility decision for each selected PR
- Files and hunks ported
- Bedrock/OpenAI/ACP adaptations made, if any
- Warp-only changes intentionally skipped
- Dependencies or generated files changed
- Validation commands and results
- Any follow-up work or manual testing needed
If a requested PR is too intertwined with Warp's service architecture, recommend a smaller follow-up port or explain which local hunks can be extracted safely.
@@ -1,387 +1,216 @@
---
name: update-galaxy-with-latest-warp
description: Merge the latest changes from upstream Warp into Galaxy, preserving Galaxy's identity, branding, and AI provider architecture (Bedrock / OpenAI-LiteLLM only). This skill fetches the latest Warp master, merges it in, resolves conflicts in favor of Galaxy's customizations, strips any Warp API/AI/cloud code, and then iteratively repairs the build until `cargo build` succeeds cleanly.
description: Selectively synchronize Galaxy with upstream Warp by updating Rust dependencies and porting eligible terminal, UI, editor, and local core improvements. Do not perform a blanket merge or import Warp API, proprietary AI, cloud auth, telemetry, or billing code.
---
# Update Galaxy with Latest Warp
# Selectively Update Galaxy from Warp
This skill merges the latest upstream Warp changes into Galaxy while preserving everything that makes Galaxy what it is. Galaxy is a fork of Warp that:
Galaxy is a fork of Warp, but it intentionally does not use Warp's proprietary service architecture. The default update strategy is therefore **not** `git merge warp/master`. It is a controlled, selective synchronization focused on code that can run locally in Galaxy.
- Uses **Amazon Bedrock** and/or **OpenAI/LiteLLM** as its AI providers — NEVER Warp's proprietary AI API
- Has its own branding (Galaxy, not Warp) in user-facing surfaces
- Does NOT use Warp's cloud authentication, telemetry, or billing
- Maintains its own deployment pipeline (Hermes)
- Keeps all Galaxy-specific features, settings, and customizations intact
## Non-negotiable exclusions
---
Never import or reintroduce upstream code that depends on:
## Phase 1: Fetch Latest Warp
- Warp's proprietary AI API, agent backend, or hosted orchestration
- Warp authentication, subscriptions, billing, teams, or cloud-only flows
- Warp telemetry or analytics that phone home to Warp
- Warp server GraphQL/API endpoints, unless the change is explicitly adapted to an existing Galaxy service
- Warp-specific deployment, channel, branding, or package identity
- Any change that would replace or bypass Galaxy's Bedrock/OpenAI provider dispatch
The `warp` remote is already configured in this repository pointing to `git@github.com:warpdotdev/warp.git`.
Galaxy's AI provider implementation remains authoritative in:
- `app/src/ai/bedrock/`
- `app/src/ai/openai/`
- `app/src/ai/provider/`
- `app/src/ai/blocklist/controller/response_stream.rs`
## Phase 0: Inspect the working tree
Before changing anything:
```bash
git --no-optional-locks status --short --branch
```
Do not overwrite or reset user changes. If the worktree contains unrelated modifications, keep them intact and make the update in a separate branch or ask the user before proceeding.
## Phase 1: Update Rust dependencies separately
A dependency update is independent from synchronizing upstream source code. Run it from the Galaxy repository root:
```bash
cargo update
```
Then inspect the dependency diff:
```bash
git diff -- Cargo.lock
cargo check --workspace
```
If `cargo update` causes unrelated or excessive churn, do not blindly keep it. Prefer targeted updates for a specific dependency:
```bash
cargo update -p <package>
```
If the lockfile update is useful but a package causes breakage, revert only that package's update or restore the lockfile and update dependencies incrementally. Never use a lockfile regeneration as a substitute for source synchronization.
`cargo update` updates crates.io/git dependency resolution; it does **not** bring terminal or UI source changes from Warp. Those require the selective workflow below.
## Phase 2: Fetch and inventory upstream changes
The `warp` remote may be configured, but fetching is read-only with respect to Galaxy's branches:
```bash
git fetch warp master
git log --oneline -30 warp/master
```
Verify the fetch succeeded and note the latest commit:
Compare upstream with the Galaxy base without merging:
```bash
git log --oneline -1 warp/master
git diff --stat HEAD...warp/master
git diff --name-status HEAD...warp/master
```
---
Classify candidate changes by path and commit. Good candidates generally include:
## Phase 2: Create a Working Branch
- `app/src/terminal/` and terminal model/emulation code, excluding agent/provider integrations
- `crates/galaxy_terminal/` or the corresponding upstream terminal crate
- `crates/galaxyui/`, `crates/galaxyui_core/`, and UI components
- `crates/editor/`, `crates/sum_tree/`, completers, parsers, and local utilities
- Local-only bug fixes and platform behavior fixes
Create a dedicated branch for the merge work:
Reject candidates that touch or depend on:
- Warp AI/server/auth/telemetry/billing modules
- Warp-specific GraphQL/API schema or cloud synchronization
- Galaxy identity, channels, settings, deployment, or Bedrock/OpenAI files
- Broad refactors whose dependency surface cannot be isolated safely
For a candidate commit, inspect before applying it:
```bash
git checkout -b update-from-warp-$(date +%Y%m%d) master
git show --stat --summary <commit>
git show --format=fuller --find-renames <commit> -- <path>
```
This ensures master stays clean until we have a working build.
## Phase 3: Apply only selected changes
---
## Phase 3: Merge Warp into Galaxy
Perform the merge, expecting conflicts:
Create a working branch before porting source changes:
```bash
git merge warp/master --no-commit --no-ff
git switch -c update-from-warp-$(date +%Y%m%d)
```
Using `--no-commit` so we can inspect and fix everything before committing.
Prefer, in order:
---
1. A focused upstream commit with a small, eligible file set:
```bash
git cherry-pick -n <commit>
```
2. A file- or hunk-level patch:
```bash
git diff <base> <commit> -- <eligible-paths> | git apply --3way
```
3. A manual port when Galaxy renamed paths or diverged substantially.
## Phase 4: Resolve Conflicts — Galaxy Always Wins on Identity
Do **not** cherry-pick a commit merely because it includes one useful terminal fix. If a commit mixes terminal code with Warp API/AI/cloud changes, extract only the eligible hunks or manually port the local change.
When resolving merge conflicts, follow these **non-negotiable rules**:
After each selected change:
### 4a. Files Where Galaxy ALWAYS Wins (keep ours)
For these files/patterns, always take Galaxy's version (`--ours`):
- `app/Cargo.toml` — Galaxy's version, package name, binary targets
- `Cargo.lock` — Will be regenerated anyway
- `AGENTS.md` / `CLAUDE.md` — Galaxy's agent instructions
- `.agents/` — Galaxy's skill definitions
- `script/build-and-deploy-hermes*` — Galaxy's deploy pipeline
- `script/install-galaxy.sh` — Galaxy's installer
- `app/channels/` — Galaxy's channel configurations and icons
- Any file under `app/src/ai/bedrock/` — Galaxy's Bedrock provider (keep ours)
- Any file under `app/src/ai/openai/` — Galaxy's OpenAI/LiteLLM provider (keep ours)
- Any file under `app/src/ai/provider/` — Galaxy's provider dispatch (keep ours)
- `app/src/ai/llms.rs` — Galaxy's model registry (keep ours)
- `app/src/settings/ai.rs` — Galaxy's AI settings (keep ours)
- `app/src/ai/blocklist/controller/response_stream.rs` — Galaxy's provider resolution (keep ours)
- Files with Samsung/Galaxy branding customizations
To resolve these in bulk:
```bash
git checkout --ours <file_path>
git add <file_path>
git diff --check
cargo check -p galaxy_terminal
cargo check -p galaxyui
cargo check -p galaxy_editor
```
### 4b. Files Where Warp Wins (take theirs)
Use the actual package name from the relevant `Cargo.toml` if it differs. For changes affecting the application, also run:
For pure infrastructure/terminal/UI improvements that don't touch AI or branding:
- `crates/galaxyui/` (formerly `warpui`) — Take Warp's UI improvements, then rename
- `crates/galaxyui_core/` — Same
- `app/src/terminal/` — Terminal emulation improvements (EXCEPT `app/src/terminal/input/agent.rs`)
- `crates/editor/` — Editor improvements
- `crates/sum_tree/` — Data structure improvements
- Pure algorithm / utility crates
For these:
```bash
git checkout --theirs <file_path>
git add <file_path>
cargo check -p galaxy
```
### 4c. Files That Need Manual Merge
## Path and naming adaptation
These require reading both versions and combining:
Upstream may still use Warp names while Galaxy has renamed crates and paths. Adapt imports to Galaxy's existing names rather than introducing new aliases or reverting Galaxy's naming:
- `app/src/ai/agent/` — Take Warp's agent logic improvements BUT ensure they route through Galaxy's provider dispatch, not Warp's API
- `app/src/ai/blocklist/` — Similar: take improvements but keep Galaxy's provider architecture
- `app/src/workspace/` — Take improvements but keep Galaxy branding
- `app/src/settings_view/` — Take UI improvements but keep Galaxy's AI settings pages
- Root `Cargo.toml` — Merge new dependencies from Warp but keep Galaxy's workspace metadata
- `warpui` → `galaxyui`
- `warpui_core` → `galaxyui_core`
- `warp_core` → `galaxy_core`
- `warp_terminal` → `galaxy_terminal`
- `warp_editor` → `galaxy_editor`
### 4d. Files/Directories to DELETE if Warp Adds Them
Preserve existing Galaxy aliases only where the codebase already requires them. Do not perform a repository-wide rename as part of an update.
If the merge introduces any of these, remove them:
## Dependency policy
- Any Warp-proprietary AI client (e.g. `app/src/ai/warp_api/`, `app/src/ai/warp_server/`)
- Warp authentication modules that phone home to `api.warp.dev`
When a selected upstream change needs a new dependency:
1. Check whether an equivalent dependency already exists in the Galaxy workspace.
2. Add only the smallest required dependency or feature.
3. Confirm it is local/client-side and not a Warp service crate.
4. Run the narrowest affected `cargo check`.
Do not add dependencies for Warp's proprietary APIs, cloud auth, telemetry, billing, or hosted AI.
## AI and service boundary review
Before accepting a patch that touches shared app or agent code, inspect all new imports and calls. It must route AI requests through Galaxy's provider dispatch. Reject or adapt code containing concepts such as:
- `api.warp.dev`, `warp.dev/v1`, Warp AI clients, hosted agent endpoints
- Warp auth tokens or Warp account/session APIs
- Warp telemetry/analytics senders
- Warp billing/subscription code
- Any new GraphQL queries targeting Warp's server for AI (model listing from Warp's API, etc.)
- Warp billing/subscription/team APIs
- Hosted orchestration/Oz dependencies
Useful checks:
```bash
git rm -r <unwanted_path>
grep -RInE 'api\.warp\.dev|warp\.dev/v1|WarpAIService|WarpAiClient|WARP_API_KEY|WARP_AUTH_TOKEN' app/src crates --include='*.rs'
```
### 4e. Naming Fixups After Merge
Existing intentional compatibility names or comments should be reviewed rather than mechanically deleted. New hits introduced by the update must be removed or adapted.
After resolving conflicts, some Warp naming may have leaked in from theirs-wins files. Do a sweep:
## Phase 4: Validation
Run focused checks first, then broader validation as appropriate:
```bash
# Check for Warp API endpoints that should not exist
grep -rn "api\.warp\.dev" app/ crates/ --include="*.rs"
grep -rn "warp\.dev/api" app/ crates/ --include="*.rs"
# Check for Warp AI service calls
grep -rn "WarpAIService\|warp_ai_service\|WarpAiClient" app/ crates/ --include="*.rs"
cargo fmt --all -- --check
git diff --check
cargo check -p galaxy
cargo check -p galaxy_terminal
cargo check -p galaxyui
cargo check -p galaxy_editor
```
Fix any hits — either remove the code or replace with Galaxy equivalents.
---
## Phase 5: Regenerate Cargo.lock
After all conflict resolution:
```bash
cargo generate-lockfile
```
Or if that fails due to errors, just delete and let the build recreate it:
```bash
rm Cargo.lock
cargo metadata --format-version 1 > /dev/null 2>&1 || true
```
---
## Phase 6: Build Repair Loop
This is the critical phase. **Keep iterating until `cargo build` succeeds.**
### Strategy
Run the build and fix errors one category at a time:
```bash
cargo build 2>&1 | head -100
```
### Common Error Categories and Fixes
**1. Missing modules / unresolved imports:**
- Warp may have added new modules. Check if they're AI/cloud related → delete them.
- If they're legitimate (terminal, UI, utilities) → keep them but ensure they compile.
- If they reference renamed crates (`warpui` vs `galaxyui`) → fix the import paths.
**2. Type mismatches in AI code:**
- Warp may have changed AI types/traits. Galaxy's AI architecture takes priority.
- If Warp added new fields to shared types used by both AI and non-AI code, add the fields but make them optional or provide Galaxy-appropriate defaults.
**3. Missing crate features:**
- New Warp code may need features not enabled in Galaxy's `Cargo.toml`.
- Add the features if they're for legitimate crates. Do NOT add features that enable Warp-proprietary functionality.
**4. Renamed/moved items:**
- Warp may have refactored. Follow their refactoring for non-AI code.
- For AI code, keep Galaxy's structure.
**5. New dependencies:**
- If Warp added a new crate to `[workspace.dependencies]`, add it to Galaxy's too (unless it's a Warp-internal crate).
**6. Compilation errors in files we took from Warp:**
- These files may reference things that exist in Warp but not Galaxy.
- Stub out or adapt as needed.
### The Loop
Repeat this cycle until clean:
```
1. cargo build 2>&1 | head -80
2. Identify the FIRST error
3. Fix it
4. Go to 1
```
When individual crate errors are isolated, use targeted checks to speed up:
```bash
cargo check -p <crate_name> 2>&1 | head -50
```
**IMPORTANT**: If you encounter more than 50 errors in a single file that all stem from Warp's AI API being absent, the correct fix is usually to **revert that file to Galaxy's version**:
```bash
git checkout HEAD~1 -- <file_path>
```
Or if the file is new from Warp and entirely AI-API-dependent, just delete it.
---
## Phase 7: Post-Build Verification
Once `cargo build` succeeds:
### 7a. Run clippy
```bash
cargo clippy --workspace --all-targets --all-features --tests -- -D warnings 2>&1 | head -100
```
Fix any warnings. Repeat until clean.
### 7b. Run formatter
For a release-ready update, run the repository-required checks:
```bash
./script/format
cargo clippy --workspace --all-targets --all-features --tests -- -D warnings
cargo build
```
### 7c. Verify no Warp API leaks
If the build fails, fix only errors caused by the selected update. Do not solve incompatibilities by importing excluded Warp service code or weakening Galaxy's provider boundaries.
```bash
grep -rn "api\.warp\.dev" app/src/ crates/ --include="*.rs" | grep -v "// ported from"
grep -rn "warp\.dev/v1" app/src/ crates/ --include="*.rs"
grep -rn "WARP_API_KEY\|WARP_AUTH_TOKEN" app/src/ crates/ --include="*.rs"
```
## Reporting
Any hits must be removed.
Report separately:
### 7d. Verify Galaxy's AI providers still work
- Dependencies updated by `cargo update`, including notable lockfile changes
- Upstream commits or patches selectively ported
- Files and functionality intentionally skipped because they were Warp-specific
- Validation commands run and their results
- Any candidate changes that need a future manual port
Ensure these files are intact and functional:
- `app/src/ai/bedrock/translator.rs` — Bedrock orchestrator
- `app/src/ai/bedrock/client.rs` — AWS SDK client
- `app/src/ai/bedrock/request_translator.rs` — Request builder
- `app/src/ai/bedrock/response_translator.rs` — Response parser
- `app/src/ai/openai/translator.rs` — OpenAI/LiteLLM orchestrator
- `app/src/ai/openai/client.rs` — HTTP client
- `app/src/ai/openai/convert.rs` — Message conversion
- `app/src/ai/openai/response_translator.rs` — SSE parser
- `app/src/ai/provider/mod.rs` — Provider dispatch
- `app/src/ai/provider/types.rs` — Shared types
- `app/src/ai/blocklist/controller/response_stream.rs``resolve_provider_config()`
Do not commit, push, or merge into `master` unless the user explicitly requests it.
### 7e. Quick smoke test
## Recommended fallback
```bash
cargo build --release 2>&1 | tail -5
```
If release build also passes, we're good.
---
## Phase 8: Commit and Report
Once everything is clean:
```bash
git add -A
git commit -m "Merge latest Warp upstream into Galaxy
Merged warp/master ($(git log --oneline -1 warp/master | cut -d' ' -f1)) into Galaxy.
Kept Galaxy's:
- AI provider architecture (Bedrock + OpenAI/LiteLLM)
- Branding and deployment pipeline
- Settings and model configuration
Took from Warp:
- Terminal emulation improvements
- UI framework updates
- Editor and utility improvements
- Bug fixes
Stripped:
- Any Warp API/cloud/auth/telemetry additions"
```
Then inform the user of:
- What was merged
- What conflicts were resolved and how
- What Warp additions were rejected/stripped
- Whether any manual follow-up is needed
Ask the user if they want to merge this branch into master:
```bash
git checkout master
git merge update-from-warp-$(date +%Y%m%d)
git push
```
---
## Critical Invariants — NEVER Violate These
1. **Galaxy's AI MUST only use Bedrock or OpenAI/LiteLLM** — defined in `app/src/ai/bedrock/` and `app/src/ai/openai/`. Warp's AI API/server calls are NEVER acceptable.
2. **Galaxy's version and package name stay as-is**`app/Cargo.toml` keeps `name = "galaxy"` and Galaxy's version number.
3. **Galaxy's binary targets stay as-is**`galaxy-oss`, `galaxy-dev`, `galaxy-preview`, `galaxy-stable`.
4. **No Warp telemetry** — Any analytics/tracking code from Warp gets deleted, not commented out.
5. **No Warp authentication flows** — Galaxy does not phone home to Warp's servers.
6. **Galaxy's deploy pipeline is untouched**`script/build-and-deploy-hermes*` and `script/install-galaxy.sh` are always kept.
7. **The build MUST succeed before this skill is considered complete** — If the build is broken, keep fixing. Do not stop.
---
## Failure Recovery
If the merge becomes unrecoverable (e.g., Warp has done a massive architectural change that breaks everything):
1. Abort the merge:
```bash
git merge --abort
```
2. Or reset the branch:
```bash
git checkout master
git branch -D update-from-warp-$(date +%Y%m%d)
```
3. Inform the user that a manual, selective port is needed instead of a full merge.
4. Suggest using the `bring-warp-feature-over` skill to cherry-pick specific improvements instead.
---
## Reference: Galaxy ↔ Warp Name Mapping
- `warp` (package) → `galaxy`
- `warp_core` → `galaxy_core`
- `warpui` → `galaxyui`
- `warpui_core` → `galaxyui_core`
- `warp_features` → `galaxy_features`
- `warp_completer` → `galaxy_completer`
- `warp_graphql_schema` → `galaxy_graphql_schema`
- `WARP_` env var prefix → `GALAXY_`
- `~/.warp/` → `~/.galaxy-ai/`
- `warp.sqlite` → `galaxy.sqlite`
- Binary names: `warp` → `galaxy-oss` (main), `galaxy-dev`, `galaxy-preview`, `galaxy-stable`
## Reference: Galaxy's AI Architecture
```
Provider dispatch: response_stream.rs → resolve_provider_config() → ProviderConfig enum
↓ Bedrock ↓ OpenAI
bedrock/translator.rs openai/translator.rs
```
- Settings: `ai.bedrock.enabled` (default true), `ai.openai.enabled` (takes priority if true)
- Multi-provider: `ai.providers[]` array with per-provider `base_url`, `api_key`, `models[]`
- Model discovery: OpenAI providers probe `/models` endpoint + `[1m]` variant detection
- Bedrock: Direct AWS SDK calls via `aws-sdk-bedrockruntime`, uses cross-region inference
This architecture is SACRED. Warp's AI changes must never replace or bypass it.
If upstream has accumulated a large architectural delta, stop doing a broad synchronization. Use the `bring-warp-feature-over` skill to identify and migrate individual eligible features. That workflow is safer for Galaxy than attempting to reconcile all of Warp's unrelated service and AI changes at once.
Generated
+140 -150
View File
@@ -124,7 +124,7 @@ dependencies = [
"futures-concurrency",
"rustc-hash 2.1.3",
"rustix 1.1.4",
"schemars 1.2.1",
"schemars 1.2.2",
"serde",
"serde_json",
"shell-words",
@@ -151,7 +151,7 @@ checksum = "d5c231915b4ab578c722eca2d1bd7df4d300bfd6cac3b8e9f0d1e3ddc95b187c"
dependencies = [
"anyhow",
"derive_more 2.1.1",
"schemars 1.2.1",
"schemars 1.2.2",
"serde",
"serde_json",
"serde_with 3.21.0",
@@ -1150,9 +1150,9 @@ dependencies = [
[[package]]
name = "async-compression"
version = "0.4.42"
version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac"
checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8"
dependencies = [
"compression-codecs",
"compression-core",
@@ -1435,9 +1435,9 @@ dependencies = [
[[package]]
name = "aws-config"
version = "1.10.0"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "701418aa459dac33e50a0f8e818e5662a16bc018a6ac7423659b70f3799d67a8"
checksum = "1b180a3c8b55960db3426d8964b8745e652466a1a49fe1a2eda828046d30b5e4"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1457,7 +1457,7 @@ dependencies = [
"bytes",
"fastrand 2.5.0",
"hex",
"http 1.4.2",
"http 1.5.0",
"p256",
"rand 0.8.7",
"sha1",
@@ -1507,9 +1507,9 @@ dependencies = [
[[package]]
name = "aws-runtime"
version = "1.9.0"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6b50a43f3ccdf331521c6d6c68b7cc9668b6e09d439ebda9569df5722324d76"
checksum = "c9007227e10b5fed2f3e0a2beff489211e2b5604c400b7a9d5d81ca9d64c24bb"
dependencies = [
"aws-credential-types",
"aws-sigv4",
@@ -1523,7 +1523,7 @@ dependencies = [
"bytes",
"bytes-utils",
"fastrand 2.5.0",
"http 1.4.2",
"http 1.5.0",
"http-body 1.1.0",
"percent-encoding",
"pin-project-lite",
@@ -1533,9 +1533,9 @@ dependencies = [
[[package]]
name = "aws-sdk-bedrockruntime"
version = "1.137.0"
version = "1.138.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d264d7d6412325fc6c77449f98363920da408fb426ce2d8b9b7c95324497c2c"
checksum = "8b802a89d3fab0f871a61779d28dc77b363f48dbe9efbef69ba67b6a071b64e0"
dependencies = [
"arc-swap",
"aws-credential-types",
@@ -1554,7 +1554,7 @@ dependencies = [
"bytes",
"fastrand 2.5.0",
"http 0.2.12",
"http 1.4.2",
"http 1.5.0",
"http-body-util",
"regex-lite",
"tracing",
@@ -1562,9 +1562,9 @@ dependencies = [
[[package]]
name = "aws-sdk-signin"
version = "1.17.0"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cb1aef0872a7ab9e035a8c14d9ee1c3bd7f86f741b3620df8e8ecfe7b0d14dd"
checksum = "cdce92c2b36186558b99c36a144b3a7343f2425b0613108d39b67e45c333170d"
dependencies = [
"arc-swap",
"aws-credential-types",
@@ -1581,16 +1581,16 @@ dependencies = [
"bytes",
"fastrand 2.5.0",
"http 0.2.12",
"http 1.4.2",
"http 1.5.0",
"regex-lite",
"tracing",
]
[[package]]
name = "aws-sdk-sso"
version = "1.104.0"
version = "1.105.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b53416d16c278234845392e38d93bd4481d2f09daa0f005a2277f0aa91f59c22"
checksum = "6ffd0fbe7873cb548a7aa60f9573c268fff94155397fd4f14dc9f1ecaaab8516"
dependencies = [
"arc-swap",
"aws-credential-types",
@@ -1607,16 +1607,16 @@ dependencies = [
"bytes",
"fastrand 2.5.0",
"http 0.2.12",
"http 1.4.2",
"http 1.5.0",
"regex-lite",
"tracing",
]
[[package]]
name = "aws-sdk-ssooidc"
version = "1.106.0"
version = "1.107.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc9b706c3305ed0285d5b1b696c747aa34950f830fb03e3e6c76890f99b9f188"
checksum = "175763eb222a46377df7aa257a3bca980ab3e96703fefc8f4d0b8da6ad2e254c"
dependencies = [
"arc-swap",
"aws-credential-types",
@@ -1633,16 +1633,16 @@ dependencies = [
"bytes",
"fastrand 2.5.0",
"http 0.2.12",
"http 1.4.2",
"http 1.5.0",
"regex-lite",
"tracing",
]
[[package]]
name = "aws-sdk-sts"
version = "1.109.0"
version = "1.110.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32d214cdfa5bbe17f117e76a7643fadf32a5234fb597322ef8b1fb4b2f17dbbd"
checksum = "dd8b14781dfbff48984017d57167b6ea0b6471c6920ec52b44a2677c7feb3c13"
dependencies = [
"arc-swap",
"aws-credential-types",
@@ -1660,7 +1660,7 @@ dependencies = [
"aws-types",
"fastrand 2.5.0",
"http 0.2.12",
"http 1.4.2",
"http 1.5.0",
"regex-lite",
"tracing",
]
@@ -1681,7 +1681,7 @@ dependencies = [
"hex",
"hmac 0.13.0",
"http 0.2.12",
"http 1.4.2",
"http 1.5.0",
"percent-encoding",
"sha2 0.11.0",
"time",
@@ -1723,7 +1723,7 @@ dependencies = [
"bytes-utils",
"futures-core",
"futures-util",
"http 1.4.2",
"http 1.5.0",
"http-body 1.1.0",
"http-body-util",
"percent-encoding",
@@ -1742,7 +1742,7 @@ dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
"h2",
"http 1.4.2",
"http 1.5.0",
"hyper",
"hyper-rustls",
"hyper-util",
@@ -1791,9 +1791,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime"
version = "1.12.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bea94a9ff8464016338c851e24b472d7131c388c88898a502e781815b2ee6045"
checksum = "07505b34e8f4b3591a4fa69e9792b52289b95488dbbc68c3c0075b7bedb245e1"
dependencies = [
"aws-smithy-async",
"aws-smithy-http",
@@ -1805,7 +1805,7 @@ dependencies = [
"bytes",
"fastrand 2.5.0",
"http 0.2.12",
"http 1.4.2",
"http 1.5.0",
"http-body 0.4.6",
"http-body 1.1.0",
"http-body-util",
@@ -1817,16 +1817,16 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime-api"
version = "1.13.0"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ed1ebe6e0a95ea84570225f5a8208dec4b8f77e61a9b0d6f51773fcb4612f0"
checksum = "3b98f2e1fd67ec06618f9c291e5e495a468e60519e44c9c1979cd0521f3affdb"
dependencies = [
"aws-smithy-async",
"aws-smithy-runtime-api-macros",
"aws-smithy-types",
"bytes",
"http 0.2.12",
"http 1.4.2",
"http 1.5.0",
"pin-project-lite",
"tokio",
"tracing",
@@ -1852,7 +1852,7 @@ checksum = "7d56e0a4e53127a632224e43633b0fe045fa9e1e3cfc68b9830f1115e103f910"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
"http 1.4.2",
"http 1.5.0",
]
[[package]]
@@ -1866,7 +1866,7 @@ dependencies = [
"bytes-utils",
"futures-core",
"http 0.2.12",
"http 1.4.2",
"http 1.5.0",
"http-body 0.4.6",
"http-body 1.1.0",
"http-body-util",
@@ -1918,7 +1918,7 @@ dependencies = [
"bytes",
"form_urlencoded",
"futures-util",
"http 1.4.2",
"http 1.5.0",
"http-body 1.1.0",
"http-body-util",
"hyper",
@@ -1949,7 +1949,7 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
dependencies = [
"bytes",
"futures-core",
"http 1.4.2",
"http 1.5.0",
"http-body 1.1.0",
"http-body-util",
"mime",
@@ -1970,7 +1970,7 @@ dependencies = [
"axum-core",
"bytes",
"futures-util",
"http 1.4.2",
"http 1.5.0",
"http-body 1.1.0",
"http-body-util",
"mime",
@@ -2403,7 +2403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a813de7f2bbedb7dce265b64f1cf5908ebe4d56281ece8d847e98113788b9b0"
dependencies = [
"rust_decimal",
"schemars 1.2.1",
"schemars 1.2.2",
"serde",
"utf8-width",
]
@@ -2694,9 +2694,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.3.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8"
checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -2851,9 +2851,9 @@ dependencies = [
[[package]]
name = "clang-sys"
version = "1.8.1"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
dependencies = [
"glob",
"libc",
@@ -2884,9 +2884,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.6.7"
version = "4.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8b397918185f0161ff3d6fcaa9e4bfc09b8367caf6e1d4a2848e5477ed027b"
checksum = "b1f84a88507dbd05c695f2cb5e8558e747179134005e9893882dec964190ed89"
dependencies = [
"clap",
]
@@ -2956,7 +2956,7 @@ dependencies = [
"log",
"persistence",
"regex",
"schemars 1.2.1",
"schemars 1.2.2",
"serde",
"serde_json",
"serde_regex",
@@ -2999,7 +2999,7 @@ dependencies = [
"itertools 0.14.0",
"lasso",
"pathfinder_geometry",
"schemars 1.2.1",
"schemars 1.2.2",
"serde",
"session-sharing-protocol",
"settings_value",
@@ -4500,13 +4500,13 @@ dependencies = [
[[package]]
name = "displaydoc"
version = "0.2.6"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
"syn 3.0.3",
]
[[package]]
@@ -4630,9 +4630,9 @@ dependencies = [
[[package]]
name = "either"
version = "1.16.0"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
[[package]]
name = "elliptic-curve"
@@ -4681,7 +4681,7 @@ dependencies = [
"cc",
"memchr",
"rustc_version",
"toml 1.1.3+spec-1.1.0",
"toml 1.1.4+spec-1.1.0",
"vswhom",
"winreg",
]
@@ -4867,11 +4867,10 @@ dependencies = [
[[package]]
name = "event-listener"
version = "5.4.1"
version = "5.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
@@ -5269,13 +5268,13 @@ dependencies = [
[[package]]
name = "foreign-types-macros"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
"syn 3.0.3",
]
[[package]]
@@ -5679,7 +5678,7 @@ dependencies = [
"gloo",
"handlebars",
"hex",
"http 1.4.2",
"http 1.5.0",
"http_client",
"http_server",
"hyper",
@@ -5763,7 +5762,7 @@ dependencies = [
"rquickjs",
"rust-embed 8.12.0",
"rustls",
"schemars 1.2.1",
"schemars 1.2.2",
"security-framework-sys",
"serde",
"serde-bytes-repr",
@@ -5948,7 +5947,7 @@ dependencies = [
"galaxyui_core",
"galaxyui_extras",
"getset",
"http 1.4.2",
"http 1.5.0",
"instant",
"inventory",
"itertools 0.14.0",
@@ -5962,7 +5961,7 @@ dependencies = [
"rand 0.8.7",
"regex",
"reqwest 0.13.4",
"schemars 1.2.1",
"schemars 1.2.2",
"serde",
"serde_json",
"serde_with 2.3.3",
@@ -6075,7 +6074,7 @@ dependencies = [
"galaxy_core",
"galaxy_graphql_schema",
"graphql-ws-client",
"http 1.4.2",
"http 1.5.0",
"http_client",
"instant",
"log",
@@ -6219,7 +6218,7 @@ dependencies = [
"galaxy_graphql",
"galaxy_isolation_platform",
"galaxyui_core",
"http 1.4.2",
"http 1.5.0",
"http_client",
"instant",
"itertools 0.14.0",
@@ -6232,7 +6231,7 @@ dependencies = [
"pathfinder_geometry",
"rand 0.8.7",
"reqwest 0.13.4",
"schemars 1.2.1",
"schemars 1.2.2",
"serde",
"serde_json",
"session-sharing-protocol",
@@ -6475,7 +6474,7 @@ dependencies = [
"rstar",
"rust-embed 8.12.0",
"rustc-hash 2.1.3",
"schemars 1.2.1",
"schemars 1.2.2",
"serde",
"serde_json",
"settings_value",
@@ -7266,7 +7265,7 @@ dependencies = [
"fnv",
"futures-core",
"futures-sink",
"http 1.4.2",
"http 1.5.0",
"indexmap 2.14.0",
"slab",
"tokio",
@@ -7411,7 +7410,7 @@ checksum = "629d8f3bbeda9d148036d6b0de0a3ab947abd08ce90626327fc3547a49d59d97"
dependencies = [
"dirs 6.0.0",
"futures",
"http 1.4.2",
"http 1.5.0",
"indicatif",
"libc",
"log",
@@ -7537,9 +7536,9 @@ dependencies = [
[[package]]
name = "http"
version = "1.4.2"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
dependencies = [
"bytes",
"itoa",
@@ -7563,7 +7562,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
dependencies = [
"bytes",
"http 1.4.2",
"http 1.5.0",
]
[[package]]
@@ -7574,7 +7573,7 @@ checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"
dependencies = [
"bytes",
"futures-core",
"http 1.4.2",
"http 1.5.0",
"http-body 1.1.0",
"pin-project-lite",
]
@@ -7598,7 +7597,7 @@ dependencies = [
"futures",
"galaxy_core",
"gloo",
"http 1.4.2",
"http 1.5.0",
"log",
"oauth2",
"prevent_sleep",
@@ -7661,7 +7660,7 @@ dependencies = [
"futures-channel",
"futures-core",
"h2",
"http 1.4.2",
"http 1.5.0",
"http-body 1.1.0",
"httparse",
"httpdate",
@@ -7678,7 +7677,7 @@ version = "0.27.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
dependencies = [
"http 1.4.2",
"http 1.5.0",
"hyper",
"hyper-util",
"rustls",
@@ -7714,7 +7713,7 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http 1.4.2",
"http 1.5.0",
"http-body 1.1.0",
"hyper",
"ipnet",
@@ -8430,9 +8429,9 @@ dependencies = [
[[package]]
name = "jiff"
version = "0.2.34"
version = "0.2.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e184d09547b80eb7e20d141ba2fb1fbac843ca53f4cf1b31210adc4c1adc6e16"
checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc"
dependencies = [
"defmt",
"jiff-core",
@@ -8456,9 +8455,9 @@ dependencies = [
[[package]]
name = "jiff-static"
version = "0.2.34"
version = "0.2.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "323da076b7a6faf914dc677cb05a4b907742ff7375c8322c9e7f5061e5e0e9de"
checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204"
dependencies = [
"jiff-core",
"proc-macro2",
@@ -8779,9 +8778,9 @@ dependencies = [
[[package]]
name = "libgit2-sys"
version = "0.18.5+1.9.4"
version = "0.18.7+1.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "005d6ae6eac1912906073e069f7db60b1fa98e052a68227824afe3e3a1c59ca2"
checksum = "23c7391e4b9f4ffab1a624223cc1d7385ff9a678f490768add717de7ea2f4d89"
dependencies = [
"cc",
"libc",
@@ -8824,7 +8823,7 @@ dependencies = [
"bitflags 2.13.1",
"libc",
"plain",
"redox_syscall 0.9.0",
"redox_syscall 0.9.1",
]
[[package]]
@@ -9181,19 +9180,19 @@ dependencies = [
[[package]]
name = "macro_rules_attribute"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520"
checksum = "b3ae8f6d608c795738406608304d30a2dfbdc8e58e44f7ba43236da5208ded3c"
dependencies = [
"macro_rules_attribute-proc_macro",
"paste",
"pastey 0.2.3",
]
[[package]]
name = "macro_rules_attribute-proc_macro"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30"
checksum = "fc04a4c58212d57930a24bf47d3fa87485264a3a054e9c10e042eb373573ad3c"
[[package]]
name = "malloc_buf"
@@ -9313,7 +9312,7 @@ dependencies = [
"galaxy_core",
"galaxyui",
"galaxyui_extras",
"http 1.4.2",
"http 1.5.0",
"log",
"oauth2",
"pin-project-lite",
@@ -9552,7 +9551,7 @@ dependencies = [
"bytes",
"colored",
"futures-core",
"http 1.4.2",
"http 1.5.0",
"http-body 1.1.0",
"http-body-util",
"hyper",
@@ -10094,7 +10093,7 @@ dependencies = [
"base64 0.22.1",
"chrono",
"getrandom 0.2.17",
"http 1.4.2",
"http 1.5.0",
"rand 0.8.7",
"serde",
"serde_json",
@@ -10813,9 +10812,9 @@ checksum = "cb1ea499d242299d564879c8b375c11285f5a26d62d8010768909d6d099c4c5a"
[[package]]
name = "ort"
version = "2.0.0-rc.12"
version = "2.0.0-rc.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7de3af33d24a745ffb8fab904b13478438d1cd52868e6f17735ef6e1f8bf133"
checksum = "4336a1e2b38848325241c72889086886004e589b7c74f335e60a8e8db5138a0b"
dependencies = [
"ndarray 0.17.2",
"ort-sys",
@@ -10826,9 +10825,9 @@ dependencies = [
[[package]]
name = "ort-sys"
version = "2.0.0-rc.12"
version = "2.0.0-rc.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7b497d21a8b6fbb4b5a544f8fadb77e801a09ae0add9e411d31c6f89e3c1e90"
checksum = "cf211e3776eea6aec988552fa118dd746d70e1b1e5e244058d1c98015f3e5872"
dependencies = [
"hmac-sha256",
"lzma-rust2",
@@ -11308,7 +11307,7 @@ checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85"
dependencies = [
"base64 0.22.1",
"indexmap 2.14.0",
"quick-xml 0.41.0",
"quick-xml",
"serde",
"time",
]
@@ -11813,9 +11812,9 @@ dependencies = [
[[package]]
name = "psl"
version = "2.1.220"
version = "2.1.223"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de177021aa731f88221b3ae411cb89d70f66e51487c26ef9c9f65fc7250c22e8"
checksum = "c0dedad316e05de220cbf3fd8bfb69f3df8755dde2d7d479211827b595168a93"
dependencies = [
"psl-types",
]
@@ -11921,15 +11920,6 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.39.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e"
dependencies = [
"memchr",
]
[[package]]
name = "quick-xml"
version = "0.41.0"
@@ -12430,9 +12420,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.9.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759"
checksum = "07507be7b4a5f9f26eeb41eeaebb1f5a7ff29dfb29739facc21d35bf8b11c21e"
dependencies = [
"bitflags 2.13.1",
]
@@ -12631,7 +12621,7 @@ dependencies = [
"futures-core",
"futures-util",
"h2",
"http 1.4.2",
"http 1.5.0",
"http-body 1.1.0",
"http-body-util",
"hyper",
@@ -12675,7 +12665,7 @@ dependencies = [
"futures-core",
"futures-util",
"h2",
"http 1.4.2",
"http 1.5.0",
"http-body 1.1.0",
"http-body-util",
"hyper",
@@ -12827,14 +12817,14 @@ dependencies = [
"base64 0.22.1",
"chrono",
"futures",
"http 1.4.2",
"http 1.5.0",
"oauth2",
"pastey 0.2.3",
"pin-project-lite",
"process-wrap",
"reqwest 0.13.4",
"rmcp-macros",
"schemars 1.2.1",
"schemars 1.2.2",
"serde",
"serde_json",
"sse-stream",
@@ -13101,9 +13091,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.42"
version = "0.23.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138"
checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
dependencies = [
"aws-lc-rs",
"log",
@@ -13129,9 +13119,9 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
version = "1.15.0"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046"
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
dependencies = [
"web-time",
"zeroize",
@@ -13299,9 +13289,9 @@ dependencies = [
[[package]]
name = "schemars"
version = "1.2.1"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc"
checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a"
dependencies = [
"chrono",
"dyn-clone",
@@ -13314,14 +13304,14 @@ dependencies = [
[[package]]
name = "schemars_derive"
version = "1.2.1"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f"
checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.119",
"syn 3.0.3",
]
[[package]]
@@ -13512,13 +13502,13 @@ dependencies = [
[[package]]
name = "serde_derive_internals"
version = "0.29.1"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
"syn 3.0.3",
]
[[package]]
@@ -13635,7 +13625,7 @@ dependencies = [
"indexmap 1.9.3",
"indexmap 2.14.0",
"schemars 0.9.0",
"schemars 1.2.1",
"schemars 1.2.2",
"serde_core",
"serde_json",
"serde_with_macros 3.21.0",
@@ -13753,7 +13743,7 @@ dependencies = [
"galaxyui_extras",
"inventory",
"log",
"schemars 1.2.1",
"schemars 1.2.2",
"serde",
"serde_json",
"settings_value",
@@ -13766,7 +13756,7 @@ version = "0.1.0"
dependencies = [
"chrono",
"instant",
"schemars 1.2.1",
"schemars 1.2.2",
"serde",
"serde_json",
"settings_value_derive",
@@ -14499,9 +14489,9 @@ dependencies = [
[[package]]
name = "takecell"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20f34339676cdcab560c9a82300c4c2581f68b9369aedf0fae86f2ff9565ff3e"
checksum = "07dd1d452d2c3dc94a4e1c5c3c9a3cc88c2ef5926674b75881e454c4dc3a14c4"
[[package]]
name = "tantivy"
@@ -15189,13 +15179,13 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "2.7.1"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba"
checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
"syn 3.0.3",
]
[[package]]
@@ -15280,9 +15270,9 @@ dependencies = [
[[package]]
name = "toml"
version = "1.1.3+spec-1.1.0"
version = "1.1.4+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c"
checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5"
dependencies = [
"indexmap 2.14.0",
"serde_core",
@@ -15369,9 +15359,9 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.1.2+spec-1.1.0"
version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
dependencies = [
"winnow 1.0.4",
]
@@ -15415,7 +15405,7 @@ dependencies = [
"bytes",
"futures-core",
"futures-util",
"http 1.4.2",
"http 1.5.0",
"http-body 1.1.0",
"http-body-util",
"http-range-header",
@@ -15581,7 +15571,7 @@ dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http 1.4.2",
"http 1.5.0",
"httparse",
"log",
"rand 0.8.7",
@@ -15930,7 +15920,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c"
dependencies = [
"base64 0.22.1",
"http 1.4.2",
"http 1.5.0",
"httparse",
"log",
]
@@ -16523,9 +16513,9 @@ dependencies = [
[[package]]
name = "wayland-backend"
version = "0.3.15"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d"
checksum = "016ccf01d1c58b6f8999612813e17c9b2390f7d70671428869913310f83f54b8"
dependencies = [
"cc",
"downcast-rs 1.2.1",
@@ -16537,9 +16527,9 @@ dependencies = [
[[package]]
name = "wayland-client"
version = "0.31.14"
version = "0.31.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073"
dependencies = [
"bitflags 2.13.1",
"rustix 1.1.4",
@@ -16609,12 +16599,12 @@ dependencies = [
[[package]]
name = "wayland-scanner"
version = "0.31.10"
version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a"
checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0"
dependencies = [
"proc-macro2",
"quick-xml 0.39.4",
"quick-xml",
"quote",
]
@@ -16703,7 +16693,7 @@ dependencies = [
"futures-test-sink",
"futures-util",
"graphql-ws-client",
"http 1.4.2",
"http 1.5.0",
"http-body-util",
"hyper",
"hyper-util",
+16
View File
@@ -7,6 +7,22 @@ pub(crate) fn acp_model_id(agent_id: &str) -> String {
format!("acp:{}", agent_id.trim().to_ascii_lowercase())
}
pub(crate) fn acp_selection_model_id(
agent_id: &str,
values: &std::collections::BTreeMap<String, serde_json::Value>,
) -> String {
let suffix = values
.iter()
.map(|(key, value)| format!("{key}={value}"))
.collect::<Vec<_>>()
.join(";");
if suffix.is_empty() {
acp_model_id(agent_id)
} else {
format!("{}:{suffix}", acp_model_id(agent_id))
}
}
pub(crate) fn acp_launch_fingerprint(
agent_id: &str,
custom_command: &str,
+2 -2
View File
@@ -12,8 +12,8 @@ mod runtime_model;
mod transport;
pub(crate) use launch::{
acp_launch_fingerprint, acp_model_id, resolve_acp_launch, validate_acp_dispatch,
validate_acp_launch_identity,
acp_launch_fingerprint, acp_model_id, acp_selection_model_id, resolve_acp_launch,
validate_acp_dispatch, validate_acp_launch_identity,
};
pub(crate) use permissions::resolve_acp_permissions;
pub(crate) use runtime_model::AcpRuntimeModel;
+67
View File
@@ -1,3 +1,6 @@
use crate::settings::{
AISettings, AcpAgentSettings, AcpConfigOptionSettings, AcpConfigValueSettings,
};
use galaxy_acp::{AcpLaunchConfig, AcpManagerConfig, AcpSessionManager};
use galaxyui::{Entity, ModelContext, SingletonEntity};
@@ -33,6 +36,70 @@ impl AcpRuntimeModel {
});
Ok(manager)
}
pub(crate) fn normalize_config_options(
options: Vec<galaxy_acp::SessionConfigOption>,
) -> Vec<AcpConfigOptionSettings> {
options
.into_iter()
.map(|option| {
let kind = match &option.kind {
galaxy_acp::SessionConfigOptionType::Select => "select",
galaxy_acp::SessionConfigOptionType::Boolean => "boolean",
}
.to_owned();
let current_value = serde_json::to_value(&option.current_value).unwrap_or_default();
let values = option
.options
.into_iter()
.map(|value| AcpConfigValueSettings {
value: serde_json::to_value(value.value).unwrap_or_default(),
name: value.name,
description: value.description,
})
.collect();
AcpConfigOptionSettings {
id: option.id,
name: option.name,
description: option.description,
category: option
.category
.map(|category| format!("{category:?}").to_lowercase()),
kind,
current_value,
options: values,
}
})
.collect()
}
pub(crate) fn upsert_agent_settings(
settings: &mut AISettings,
agent_id: &str,
options: Vec<galaxy_acp::SessionConfigOption>,
) -> Result<(), String> {
let config_options = Self::normalize_config_options(options);
let mut agents = settings.acp_agents.value().clone();
if let Some(agent) = agents
.iter_mut()
.find(|agent| agent.id.eq_ignore_ascii_case(agent_id))
{
agent.config_options = config_options;
} else {
agents.push(AcpAgentSettings {
id: agent_id.to_owned(),
name: agent_id.to_owned(),
version: None,
description: None,
icon_url: None,
capabilities: Vec::new(),
config_options,
});
}
settings
.acp_agents
.set_value(agents, &mut settings.context())
}
}
impl Entity for AcpRuntimeModel {
+20 -4
View File
@@ -8,11 +8,11 @@ use futures::stream::FusedStream as _;
use futures::{FutureExt as _, StreamExt as _};
use galaxy_acp::{
AcpEvent, AcpPermissionPolicy, AcpRuntimeError, AcpSessionHandle, AcpSessionManager,
AcpSteeringOutcome, AcpTurnRequest, ContentBlock, McpServer, McpServerStdio, SessionId,
TextContent,
AcpSteeringOutcome, AcpTurnRequest, ContentBlock, McpServer, McpServerStdio,
SessionConfigOptionValue, SessionId, TextContent,
};
use super::launch::acp_model_id;
use super::launch::acp_selection_model_id;
use super::prompt::{prompt_content, GalaxyTerminalTools};
use super::response_translator::AcpResponseTranslator;
use crate::ai::agent::api::{self, RequestParams};
@@ -25,6 +25,7 @@ pub(crate) struct AcpSessionMetadata {
pub(crate) session_id: Option<String>,
pub(crate) can_load: bool,
pub(crate) can_steer: bool,
pub(crate) config_options: Vec<galaxy_acp::SessionConfigOption>,
}
#[derive(Clone, Debug)]
@@ -121,6 +122,15 @@ pub(crate) async fn acp_output_stream(
mcp_servers.push(server);
}
let request = AcpTurnRequest {
config_values: backend
.config_values
.into_iter()
.filter_map(|(key, value)| {
serde_json::from_value::<SessionConfigOptionValue>(value)
.ok()
.map(|value| (key, value))
})
.collect(),
conversation_key: conversation_id,
session_id: backend.session_id.map(SessionId::from),
cwd,
@@ -222,6 +232,7 @@ pub(crate) async fn acp_output_stream(
session_id,
can_load,
can_steer,
..
} = &event
{
if let Ok(mut metadata) = session_metadata.lock() {
@@ -230,6 +241,11 @@ pub(crate) async fn acp_output_stream(
metadata.can_steer = *can_steer;
}
}
if let AcpEvent::ConfigOptions { options } = &event {
if let Ok(mut metadata) = session_metadata.lock() {
metadata.config_options = options.clone();
}
}
match translator.translate(event) {
Ok(response_events) => {
for response_event in response_events {
@@ -272,7 +288,7 @@ fn response_translator(
task_id,
params.tasks.is_empty(),
user_query,
acp_model_id(&backend.agent_id),
acp_selection_model_id(&backend.agent_id, &backend.config_values),
)
}
+16
View File
@@ -1220,6 +1220,22 @@ impl BlocklistAIHistoryModel {
agent_id: agent_id.to_string(),
launch_fingerprint,
session_id: None,
config_values: settings
.acp_agents
.value()
.iter()
.find(|agent| agent.id.eq_ignore_ascii_case(agent_id))
.and_then(|agent| {
agent
.config_options
.iter()
.find(|option| option.category.as_deref() == Some("model"))
})
.map(|option| {
std::iter::once((option.id.clone(), option.current_value.clone()))
.collect()
})
.unwrap_or_default(),
})
} else {
AgentBackend::Provider
+54 -1
View File
@@ -21,7 +21,7 @@ use crate::auth::auth_manager::{AuthManager, AuthManagerEvent};
use crate::auth::AuthStateProvider;
use crate::network::{NetworkStatus, NetworkStatusEvent, NetworkStatusKind};
use crate::server::server_api::ServerApiProvider;
use crate::settings::{BedrockModelConfig, OpenAIModelConfig};
use crate::settings::{AcpAgentSettings, BedrockModelConfig, OpenAIModelConfig};
use crate::user_config::{WarpConfig, WarpConfigUpdateEvent};
use crate::workspaces::user_workspaces::{UserWorkspaces, UserWorkspacesEvent};
use crate::{report_error, AISettings};
@@ -922,6 +922,7 @@ impl LLMPreferences {
self.openai_provider_routing.clear();
let settings = AISettings::as_ref(ctx);
self.inject_acp_models(ctx);
if !*settings.openai_enabled.value() {
return;
}
@@ -1034,6 +1035,58 @@ impl LLMPreferences {
log::info!("[openai/litellm] Injected {total_injected} model(s) into available choices");
}
#[cfg(not(target_family = "wasm"))]
fn inject_acp_models(&mut self, ctx: &AppContext) {
let settings = AISettings::as_ref(ctx);
for agent in settings.acp_agents.value() {
let model_option = agent
.config_options
.iter()
.find(|option| option.category.as_deref() == Some("model"));
let Some(model_option) = model_option else { continue };
let secondary = agent.config_options.iter().filter(|option| {
matches!(option.category.as_deref(), Some("mode") | Some("thought_level"))
});
for value in &model_option.options {
let suffix = secondary
.clone()
.filter_map(|option| option.options.first().map(|v| v.name.clone()))
.collect::<Vec<_>>();
let display_name = if suffix.is_empty() {
value.name.clone()
} else {
format!("{} ({})", value.name, suffix.join(", "))
};
let id = format!("acp:{}:{}={}", agent.id, model_option.id, value.value);
let info = LLMInfo {
id: LLMId::from(id.as_str()),
display_name,
base_model_name: value.name.clone(),
reasoning_level: None,
usage_metadata: LLMUsageMetadata {
request_multiplier: 1,
credit_multiplier: None,
},
description: Some(agent.name.clone()),
disable_reason: None,
vision_supported: false,
spec: None,
provider: LLMProvider::Unknown,
host_configs: HashMap::new(),
discount_percentage: None,
context_window: LLMContextWindow::default(),
};
self.models_by_feature.agent_mode.choices.push(info.clone());
self.models_by_feature.coding.choices.push(info.clone());
if let Some(ref mut cli) = self.models_by_feature.cli_agent {
cli.choices.push(info);
}
}
}
}
}
/// Ensures the default model ID in each feature's choices still points to
/// an existing entry. If the default was removed (e.g. provider disabled),
/// switch to the first remaining choice.
+55
View File
@@ -901,6 +901,50 @@ pub struct OpenAIProviderConfig {
impl settings_value::SettingsValue for OpenAIProviderConfig {}
/// Cached metadata and runtime session options for an ACP agent.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, schemars::JsonSchema)]
pub struct AcpAgentSettings {
pub id: String,
pub name: String,
#[serde(default)]
pub version: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub icon_url: Option<String>,
#[serde(default)]
pub capabilities: Vec<String>,
#[serde(default)]
pub config_options: Vec<AcpConfigOptionSettings>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, schemars::JsonSchema)]
pub struct AcpConfigOptionSettings {
pub id: String,
pub name: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub category: Option<String>,
pub kind: String,
#[serde(default)]
pub current_value: serde_json::Value,
#[serde(default)]
pub options: Vec<AcpConfigValueSettings>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, schemars::JsonSchema)]
pub struct AcpConfigValueSettings {
pub value: serde_json::Value,
pub name: String,
#[serde(default)]
pub description: Option<String>,
}
impl settings_value::SettingsValue for AcpAgentSettings {}
// Nested ACP discovery data is intentionally persisted as one setting so refreshes are atomic.
define_settings_group!(AISettings, settings: [
// If `false`, all AI features are disabled.
is_any_ai_enabled: IsAnyAIEnabled {
@@ -1270,6 +1314,17 @@ define_settings_group!(AISettings, settings: [
description: "Arguments passed to the local Agent Client Protocol agent executable.",
feature_flag: FeatureFlag::AgentClientProtocol,
}
// Cached ACP registry and runtime discovery data. Values are refreshed when the agent is queried.
acp_agents: AcpAgents {
type: Vec<AcpAgentSettings>,
default: Vec::new(),
supported_platforms: SupportedPlatforms::OR(SupportedPlatforms::MAC.into(), SupportedPlatforms::LINUX.into()),
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "ai.acp.agents",
description: "Cached ACP agent metadata, capabilities, and session configuration options.",
feature_flag: FeatureFlag::AgentClientProtocol,
}
// Whether to use locally loaded AWS credentials for Bedrock-enabled requests.
bedrock_enabled: BedrockEnabled {
type: bool,
+11
View File
@@ -7824,6 +7824,17 @@ impl SettingsWidget for ACPSettingsWidget {
is_enabled,
app,
));
let discovered = settings.acp_agents.value();
if let Some(agent) = discovered
.iter()
.find(|agent| agent.id.eq_ignore_ascii_case(settings.acp_agent_id.value()))
{
column.add_child(render_ai_setting_description(
&format!("Discovered {} ACP configuration option(s) for {}. Options are refreshed from the running agent and cached in settings.toml.", agent.config_options.len(), agent.name),
is_enabled,
app,
));
}
column.finish()
}
}
+8 -1
View File
@@ -1,5 +1,6 @@
use agent_client_protocol::schema::v1::{
ContentBlock, Cost, RequestPermissionRequest, SessionId, StopReason, ToolCallId, ToolCallStatus,
ContentBlock, Cost, RequestPermissionRequest, SessionConfigOption, SessionId, StopReason,
ToolCallId, ToolCallStatus,
};
use crate::PermissionDecision;
@@ -12,6 +13,10 @@ pub enum AcpEvent {
SessionStarted {
/// Agent-owned session identifier.
session_id: SessionId,
/// Agent implementation metadata from initialization.
agent_info: Option<agent_client_protocol::schema::v1::Implementation>,
/// Agent capabilities from initialization.
capabilities: agent_client_protocol::schema::v1::AgentCapabilities,
/// Whether this agent advertised `session/load`.
can_load: bool,
/// Whether this agent advertised the Codex `_session/steering`
@@ -65,6 +70,8 @@ pub enum AcpEvent {
/// Bounded, control-sequence-free output supplied by this update.
output: Option<String>,
},
/// The agent advertised or changed the complete session configuration.
ConfigOptions { options: Vec<SessionConfigOption> },
/// Context-window or cost information changed.
Usage {
/// Tokens currently in context.
+2 -1
View File
@@ -11,7 +11,8 @@ mod runtime;
pub use agent_client_protocol::schema::v1::{
ContentBlock, Cost, ImageContent, McpServer, McpServerHttp, McpServerStdio, PermissionOptionId,
SessionId, StopReason, TextContent, ToolCallId, ToolCallStatus,
SessionConfigOption, SessionConfigOptionCategory, SessionConfigOptionValue, SessionId,
StopReason, TextContent, ToolCallId, ToolCallStatus,
};
pub use config::{
AcpAgentPreset, AcpLaunchConfig, AcpManagerConfig, CODEX_ACP_NPM_VERSION, OPENCODE_NPM_VERSION,
+147 -9
View File
@@ -10,8 +10,9 @@ use agent_client_protocol::schema::v1::{
AuthMethod, AuthMethodId, AuthenticateRequest, CancelNotification, ClientCapabilities,
ContentBlock, Implementation, InitializeRequest, InitializeResponse, LoadSessionRequest,
McpServer, Meta, NewSessionRequest, PromptRequest, PromptResponse, RequestPermissionOutcome,
RequestPermissionRequest, RequestPermissionResponse, SessionId, SessionNotification,
SessionUpdate, StopReason, TextContent, ToolCallContent,
RequestPermissionRequest, RequestPermissionResponse, SessionConfigOption,
SessionConfigOptionValue, SessionId, SessionNotification, SessionUpdate,
SetSessionConfigOptionRequest, StopReason, TextContent, ToolCallContent,
};
use agent_client_protocol::schema::ProtocolVersion;
use agent_client_protocol::{
@@ -19,6 +20,7 @@ use agent_client_protocol::{
};
use async_channel::{Receiver, Sender};
use futures::channel::oneshot;
use futures::future::{self, Either, FutureExt as _};
use serde::{Deserialize, Serialize};
use thiserror::Error;
@@ -87,6 +89,8 @@ pub enum AcpSteeringOutcome {
/// One prompt turn to run on an ACP session.
#[derive(Clone, Debug, PartialEq)]
pub struct AcpTurnRequest {
/// Selected ACP session configuration values keyed by agent-provided option ID.
pub config_values: std::collections::BTreeMap<String, SessionConfigOptionValue>,
/// Stable Galaxy-side key used to serialize turns for one conversation.
pub conversation_key: String,
/// Existing agent session to load. `None` creates a new session.
@@ -115,6 +119,7 @@ impl AcpTurnRequest {
prompt: Vec<ContentBlock>,
) -> Self {
Self {
config_values: std::collections::BTreeMap::new(),
conversation_key: conversation_key.into(),
session_id: None,
cwd: cwd.into(),
@@ -302,6 +307,8 @@ impl std::fmt::Debug for AcpSessionManager {
struct ManagerInner {
command_tx: Sender<Command>,
agent_info: Mutex<Option<Implementation>>,
agent_capabilities: Mutex<Option<agent_client_protocol::schema::v1::AgentCapabilities>>,
launch: AcpLaunchConfig,
alive: AtomicBool,
terminal_error: Mutex<Option<String>>,
@@ -323,12 +330,15 @@ impl AcpSessionManager {
let (command_tx, command_rx) = async_channel::unbounded();
let inner = Arc::new(ManagerInner {
command_tx: command_tx.clone(),
agent_info: Mutex::new(None),
agent_capabilities: Mutex::new(None),
launch: config.launch.clone(),
alive: AtomicBool::new(true),
terminal_error: Mutex::new(None),
});
let worker_state = Arc::downgrade(&inner);
let manager_for_worker = Arc::clone(&inner);
thread::Builder::new()
.name("galaxy-acp-runtime".to_owned())
.spawn(move || {
@@ -336,6 +346,7 @@ impl AcpSessionManager {
config,
command_rx.clone(),
command_tx,
manager_for_worker,
));
let terminal_error = result
.err()
@@ -359,6 +370,48 @@ impl AcpSessionManager {
&self.inner.launch
}
/// Returns the implementation metadata advertised during initialization.
#[must_use]
pub fn agent_info(&self) -> Option<Implementation> {
self.inner
.agent_info
.lock()
.ok()
.and_then(|info| info.clone())
}
/// Returns the capabilities advertised during initialization.
#[must_use]
pub fn agent_capabilities(
&self,
) -> Option<agent_client_protocol::schema::v1::AgentCapabilities> {
self.inner
.agent_capabilities
.lock()
.ok()
.and_then(|capabilities| capabilities.clone())
}
/// Discovers the current configuration options by creating a temporary ACP session.
pub async fn discover_config_options(
&self,
cwd: PathBuf,
mcp_servers: Vec<McpServer>,
) -> Result<Vec<SessionConfigOption>, AcpRuntimeError> {
self.ensure_alive()?;
let (result_tx, result_rx) = oneshot::channel();
self.inner
.command_tx
.send(Command::Discover {
cwd,
mcp_servers,
result: result_tx,
})
.await
.map_err(|_| self.closed_error())?;
result_rx.await.map_err(|_| self.closed_error())?
}
/// Whether the background worker and its ACP process are still available.
///
/// This becomes `false` after protocol failure, normal shutdown, or a
@@ -554,6 +607,7 @@ struct SessionSpec {
cwd: PathBuf,
additional_directories: Vec<PathBuf>,
mcp_servers: Vec<McpServer>,
config_values: std::collections::BTreeMap<String, SessionConfigOptionValue>,
}
impl From<&AcpTurnRequest> for SessionSpec {
@@ -562,6 +616,7 @@ impl From<&AcpTurnRequest> for SessionSpec {
cwd: request.cwd.clone(),
additional_directories: request.additional_directories.clone(),
mcp_servers: request.mcp_servers.clone(),
config_values: request.config_values.clone(),
}
}
}
@@ -645,6 +700,11 @@ impl ConversationState {
enum Command {
RunTurn(PendingTurn),
Discover {
cwd: PathBuf,
mcp_servers: Vec<McpServer>,
result: oneshot::Sender<Result<Vec<SessionConfigOption>, AcpRuntimeError>>,
},
SessionOpened {
conversation_key: String,
turn_id: u64,
@@ -689,6 +749,8 @@ struct RuntimeActor {
conversations: HashMap<String, ConversationState>,
can_load: bool,
can_steer: bool,
agent_info: Option<Implementation>,
agent_capabilities: agent_client_protocol::schema::v1::AgentCapabilities,
cancellation_grace_period: std::time::Duration,
}
@@ -717,6 +779,13 @@ impl RuntimeActor {
fn handle_command(&mut self, command: Command) -> Result<ActorControl, AcpRuntimeError> {
match command {
Command::RunTurn(turn) => self.queue_turn(turn)?,
Command::Discover {
cwd,
mcp_servers,
result,
} => {
self.spawn_discovery(cwd, mcp_servers, result)?;
}
Command::SessionOpened {
conversation_key,
turn_id,
@@ -798,6 +867,30 @@ impl RuntimeActor {
self.start_active_turn(&key)
}
fn spawn_discovery(
&self,
cwd: PathBuf,
mcp_servers: Vec<McpServer>,
result: oneshot::Sender<Result<Vec<SessionConfigOption>, AcpRuntimeError>>,
) -> Result<(), AcpRuntimeError> {
let connection = self.connection.clone();
self.connection
.spawn(async move {
let response = connection
.send_request(NewSessionRequest::new(cwd).mcp_servers(mcp_servers))
.block_task()
.await;
let result_value = match response {
Ok(response) => Ok(response.config_options.unwrap_or_default()),
Err(error) => Err(AcpRuntimeError::Protocol(error.to_string())),
};
let _ = result.send(result_value);
Ok(())
})
.map_err(|error| AcpRuntimeError::Protocol(error.to_string()))?;
Ok(())
}
fn start_active_turn(&mut self, conversation_key: &str) -> Result<(), AcpRuntimeError> {
let (ready, session_id, turn_id, request, events) = {
let state = self
@@ -834,7 +927,14 @@ impl RuntimeActor {
permission_policy: request.permission_policy,
},
);
emit_session_started(&events, session_id.clone(), self.can_load, self.can_steer);
emit_session_started(
&events,
session_id.clone(),
self.can_load,
self.can_steer,
self.agent_info.clone(),
self.agent_capabilities.clone(),
);
self.spawn_prompt(
conversation_key.to_owned(),
turn_id,
@@ -883,6 +983,7 @@ impl RuntimeActor {
let cwd = request.cwd.clone();
let additional_directories = request.additional_directories.clone();
let mcp_servers = request.mcp_servers.clone();
let config_values = request.config_values.clone();
let suppressed_session_id = requested_session_id.clone();
let replay_session_id = requested_session_id.clone();
let spawn_result = self.connection.spawn(async move {
@@ -904,15 +1005,25 @@ impl RuntimeActor {
.map(|_| ())
},
move || async move {
connection
let response = connection
.send_request(
NewSessionRequest::new(cwd)
.additional_directories(additional_directories)
.mcp_servers(mcp_servers),
)
.block_task()
.await
.map(|response| response.session_id)
.await?;
for (config_id, value) in config_values {
connection
.send_request(SetSessionConfigOptionRequest::new(
response.session_id.clone(),
config_id,
value,
))
.block_task()
.await?;
}
Ok(response.session_id)
},
)
.await;
@@ -989,7 +1100,14 @@ impl RuntimeActor {
permission_policy,
},
);
emit_session_started(&events, session_id.clone(), self.can_load, self.can_steer);
emit_session_started(
&events,
session_id.clone(),
self.can_load,
self.can_steer,
self.agent_info.clone(),
self.agent_capabilities.clone(),
);
if cancelled {
let _ = events.try_send(AcpEvent::Finished {
stop_reason: StopReason::Cancelled,
@@ -1331,6 +1449,7 @@ async fn run_connection_supervised(
config: AcpManagerConfig,
command_rx: Receiver<Command>,
command_tx: Sender<Command>,
manager: Arc<ManagerInner>,
) -> Result<(), AcpRuntimeError> {
let initialization_timeout = config.initialization_timeout;
let authentication_timeout = config.authentication_timeout;
@@ -1343,6 +1462,7 @@ async fn run_connection_supervised(
command_tx,
initialized_tx,
authenticated_tx,
manager,
),
initialized_rx,
authenticated_rx,
@@ -1410,6 +1530,7 @@ async fn run_connection(
command_tx: Sender<Command>,
initialized: oneshot::Sender<()>,
authenticated: oneshot::Sender<()>,
manager: Arc<ManagerInner>,
) -> Result<(), AcpRuntimeError> {
let router = Arc::new(EventRouter::default());
let permission_handler = Arc::clone(&config.permission_handler);
@@ -1472,6 +1593,13 @@ async fn run_connection(
}
let _ = initialized.send(());
if let Ok(mut agent_info) = manager.agent_info.lock() {
*agent_info = response.agent_info.clone();
}
if let Ok(mut capabilities) = manager.agent_capabilities.lock() {
*capabilities = Some(response.agent_capabilities.clone());
}
if let Some(request) = authentication_request(
&response.auth_methods,
config.launch.preferred_auth_method.as_ref(),
@@ -1481,6 +1609,7 @@ async fn run_connection(
connection.send_request(request).block_task().await?;
}
let _ = authenticated.send(());
RuntimeActor {
connection,
command_rx,
@@ -1489,6 +1618,8 @@ async fn run_connection(
conversations: HashMap::new(),
can_load: response.agent_capabilities.load_session,
can_steer: supports_steering(&response),
agent_info: response.agent_info,
agent_capabilities: response.agent_capabilities,
cancellation_grace_period: config.cancellation_grace_period,
}
.run()
@@ -1604,6 +1735,9 @@ fn event_from_session_update(update: SessionUpdate) -> Option<AcpEvent> {
output,
})
}
SessionUpdate::ConfigOptionUpdate(update) => Some(AcpEvent::ConfigOptions {
options: update.config_options,
}),
SessionUpdate::UsageUpdate(usage) => Some(AcpEvent::Usage {
used: usage.used,
size: usage.size,
@@ -1615,7 +1749,6 @@ fn event_from_session_update(update: SessionUpdate) -> Option<AcpEvent> {
SessionUpdate::Plan(_)
| SessionUpdate::AvailableCommandsUpdate(_)
| SessionUpdate::CurrentModeUpdate(_)
| SessionUpdate::ConfigOptionUpdate(_)
| SessionUpdate::SessionInfoUpdate(_) => None,
// `SessionUpdate` is non-exhaustive so newer stable protocol updates
// remain forward-compatible and can be added to the visible surface.
@@ -2084,9 +2217,13 @@ fn emit_session_started(
session_id: SessionId,
can_load: bool,
can_steer: bool,
agent_info: Option<Implementation>,
capabilities: agent_client_protocol::schema::v1::AgentCapabilities,
) {
let _ = events.try_send(AcpEvent::SessionStarted {
session_id,
agent_info,
capabilities,
can_load,
can_steer,
});
@@ -2115,7 +2252,8 @@ fn fail_queued_commands(command_rx: &Receiver<Command>, message: &str) {
| Command::PromptFinished { .. }
| Command::ForceTeardown { .. }
| Command::ConnectionClosed
| Command::Shutdown => {}
| Command::Shutdown
| Command::Discover { .. } => {}
}
}
}
+1
View File
@@ -20,6 +20,7 @@ diesel = { workspace = true, features = [
# make it optional.
diesel_migrations = "2.2.0"
serde.workspace = true
serde_json.workspace = true
warp_multi_agent_api.workspace = true
[dev-dependencies]
+6 -1
View File
@@ -1,6 +1,6 @@
//! These types are named after the database tables, and are used to represent specific queries.
use std::collections::{HashMap, HashSet};
use std::collections::{BTreeMap, HashMap, HashSet};
use chrono::NaiveDateTime;
use diesel::prelude::*;
@@ -1046,6 +1046,7 @@ impl AgentBackend {
agent_id: acp.agent_id.clone(),
launch_fingerprint: acp.launch_fingerprint.clone(),
session_id: None,
config_values: acp.config_values.clone(),
}),
}
}
@@ -1064,6 +1065,10 @@ pub struct AcpConversationData {
pub launch_fingerprint: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
/// ACP session configuration values selected for this conversation.
/// Values are opaque to Galaxy and are keyed by the agent-provided option ID.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub config_values: BTreeMap<String, serde_json::Value>,
}
// Serializes to `conversation_data` column in `agent_conversations`.