Complete agent monitoring and Galaxy Control integration

- expose command-monitor conversations and preserve visible agent transcripts
- add bounded polling and a dedicated shell interrupt tool
- improve direct-provider images, skills, tool history, and usage handling
- package and brand Galaxy Control across releases, installers, persistence, and docs
This commit is contained in:
2026-07-29 15:04:58 -05:00
parent 100f1eff1c
commit dbfa8bcd48
172 changed files with 6357 additions and 3825 deletions
+24
View File
@@ -314,6 +314,20 @@ impl AIAgentActionType {
matches!(self, Self::WriteToLongRunningShellCommand { .. })
}
/// Returns whether this action represents an exact terminal interrupt (Ctrl+C).
///
/// Direct AI providers expose a typed `interrupt_shell_command` tool, but encode it through
/// the existing write-to-PTY protobuf for backwards compatibility. Keeping this predicate on
/// the shared action type lets execution and UI code distinguish that typed operation from
/// ordinary process input without relying on printable escape spellings from the model.
pub fn is_shell_command_interrupt(&self) -> bool {
matches!(
self,
Self::WriteToLongRunningShellCommand { input, mode, .. }
if mode.is_shell_interrupt(input)
)
}
pub fn cancelled_result(&self) -> AIAgentActionResultType {
match self {
Self::RequestCommandOutput { .. } => AIAgentActionResultType::RequestCommandOutput(
@@ -802,6 +816,12 @@ pub enum AIAgentPtyWriteMode {
}
impl AIAgentPtyWriteMode {
pub fn is_shell_interrupt(self, bytes: &[u8]) -> bool {
use galaxy_terminal::model::escape_sequences;
self == Self::Raw && bytes == [escape_sequences::C0::ETX]
}
/// Decorates input bytes according to the write mode.
pub fn decorate_bytes(
self,
@@ -928,3 +948,7 @@ impl FileEdit {
}
}
}
#[cfg(test)]
#[path = "mod_tests.rs"]
mod tests;
+12
View File
@@ -0,0 +1,12 @@
use galaxy_terminal::model::escape_sequences;
use super::AIAgentPtyWriteMode;
#[test]
fn raw_etx_is_the_only_shell_interrupt_payload() {
assert!(AIAgentPtyWriteMode::Raw.is_shell_interrupt(&[escape_sequences::C0::ETX]));
assert!(!AIAgentPtyWriteMode::Raw.is_shell_interrupt(b"C-c"));
assert!(!AIAgentPtyWriteMode::Raw.is_shell_interrupt(br"\u0003"));
assert!(!AIAgentPtyWriteMode::Line.is_shell_interrupt(&[escape_sequences::C0::ETX]));
assert!(!AIAgentPtyWriteMode::Block.is_shell_interrupt(&[escape_sequences::C0::ETX]));
}
+2 -2
View File
@@ -120,8 +120,8 @@ pub fn parse_skill(path: &Path) -> Result<ParsedSkill> {
/// Parse a bundled skill markdown file.
///
/// Unlike `parse_skill`, this function does not require the path to match a known
/// skill provider directory. Bundled skills are always assigned `SkillProvider::Warp`
/// and `SkillScope::Bundled`.
/// skill provider directory. Bundled Galaxy skills retain the wire-compatible
/// `SkillProvider::Warp` variant and use `SkillScope::Bundled`.
///
/// # Arguments
/// * `path` - Path to the skill markdown file to parse
+8 -6
View File
@@ -1,6 +1,6 @@
//! Skill provider definitions and utilities.
//!
//! This module defines the supported skill providers (i.e. Agents, Claude, Codex, Warp) and their
//! This module defines the supported skill providers (i.e. Agents, Claude, Codex, Galaxy) and their
//! associated skills directory paths. It provides utilities for looking up providers
//! from paths and vice versa.
use std::path::{Path, PathBuf};
@@ -14,7 +14,9 @@ use galaxy_util::local_or_remote_path::LocalOrRemotePath;
use serde::{Deserialize, Serialize};
use strum_macros::{Display, EnumString, VariantNames};
/// Represents a skill provider/origin (Agents, Claude, Codex, or Warp).
/// Represents a skill provider/origin.
///
/// `Warp` is retained as the wire-compatible variant for Galaxy-managed skills.
#[derive(
Debug,
Clone,
@@ -62,7 +64,7 @@ pub enum SkillScope {
Home,
/// Skills from a project directory (e.g., `./repo/.agents/skills`).
Project,
/// Bundled skills distributed with Warp.
/// Bundled skills distributed with Galaxy.
Bundled,
}
@@ -82,11 +84,11 @@ impl SkillProvider {
SkillProvider::Gemini => Icon::GeminiLogo,
SkillProvider::Droid => Icon::DroidLogo,
SkillProvider::OpenCode => Icon::OpenCodeLogo,
SkillProvider::Warp
| SkillProvider::Agents
SkillProvider::Warp => Icon::GalaxyLogo,
SkillProvider::Agents
| SkillProvider::Cursor
| SkillProvider::Copilot
| SkillProvider::Github => Icon::WarpLogoLight,
| SkillProvider::Github => Icon::GalaxyLogo,
}
}
+10 -4
View File
@@ -1,3 +1,4 @@
use galaxy_core::ui::icons::Icon;
use galaxy_util::host_id::HostId;
use galaxy_util::local_or_remote_path::LocalOrRemotePath;
use galaxy_util::remote_path::RemotePath;
@@ -9,7 +10,7 @@ use super::{
};
#[test]
fn warp_home_skills_path_uses_warp_home_path() {
fn galaxy_managed_home_skills_path_uses_galaxy_home_path() {
assert_eq!(
home_skills_path(SkillProvider::Warp),
galaxy_core::paths::galaxy_home_skills_dir()
@@ -17,12 +18,12 @@ fn warp_home_skills_path_uses_warp_home_path() {
}
#[test]
fn warp_home_skill_path_is_home_warp_skill() {
let Some(warp_home_skills_dir) = galaxy_core::paths::galaxy_home_skills_dir() else {
fn galaxy_managed_home_skill_path_is_home_skill() {
let Some(galaxy_home_skills_dir) = galaxy_core::paths::galaxy_home_skills_dir() else {
eprintln!("Skipping test: home directory not available");
return;
};
let path = warp_home_skills_dir.join("my-skill").join("SKILL.md");
let path = galaxy_home_skills_dir.join("my-skill").join("SKILL.md");
assert_eq!(
get_provider_for_path(&LocalOrRemotePath::Local(path.clone())),
@@ -31,6 +32,11 @@ fn warp_home_skill_path_is_home_warp_skill() {
assert_eq!(get_scope_for_path(&path), SkillScope::Home);
}
#[test]
fn galaxy_managed_skills_use_the_galaxy_logo() {
assert_eq!(SkillProvider::Warp.icon(), Icon::GalaxyLogo);
}
#[test]
fn remote_provider_path_is_classified_by_structure() {
let path = LocalOrRemotePath::Remote(RemotePath::new(
+2 -2
View File
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
pub enum SkillReference {
/// A skill identified by the path to its SKILL.md file.
Path(LocalOrRemotePath),
/// A bundled skill distributed with Warp.
/// A bundled skill distributed with Galaxy.
BundledSkillId(String),
}
@@ -16,7 +16,7 @@ impl fmt::Display for SkillReference {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SkillReference::Path(path) => path.display_path().fmt(f),
SkillReference::BundledSkillId(id) => write!(f, "@warp-skill:{id}"),
SkillReference::BundledSkillId(id) => write!(f, "@galaxy-skill:{id}"),
}
}
}