Add installable agent lifecycle extensions
This commit is contained in:
@@ -240,6 +240,122 @@ impl App {
|
||||
]
|
||||
.spacing(10),
|
||||
);
|
||||
let mut installed_extensions = column![].spacing(10);
|
||||
if self.extensions.extensions.is_empty() {
|
||||
installed_extensions = installed_extensions.push(
|
||||
text("No agent extensions are installed.")
|
||||
.size(12)
|
||||
.color(muted_text()),
|
||||
);
|
||||
}
|
||||
for extension in &self.extensions.extensions {
|
||||
let id = extension.id.clone();
|
||||
let update_id = id.clone();
|
||||
let uninstall_id = id.clone();
|
||||
let enabled = extension.enabled;
|
||||
let toggle_control = toggle(enabled)
|
||||
.label(extension.name.clone())
|
||||
.on_toggle(move |enabled| Message::ToggleExtension(id.clone(), enabled));
|
||||
let details = format!(
|
||||
"{} • {} • {} skills • hooks: {}",
|
||||
extension.version,
|
||||
extension.author,
|
||||
extension.skill_count,
|
||||
extension.hook_names(),
|
||||
);
|
||||
let source = format!(
|
||||
"{}{} • commit {}",
|
||||
extension.source_url,
|
||||
extension
|
||||
.requested_ref
|
||||
.as_ref()
|
||||
.map_or_else(String::new, |reference| format!(" @ {reference}")),
|
||||
&extension.resolved_commit[..extension.resolved_commit.len().min(12)],
|
||||
);
|
||||
let mut row_content = column![
|
||||
row![
|
||||
toggle_control.width(Length::Fill),
|
||||
action_button("Update").on_press(Message::UpdateExtension(update_id)),
|
||||
action_button("Uninstall")
|
||||
.on_press(Message::RequestUninstallExtension(uninstall_id)),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(Alignment::Center),
|
||||
text(&extension.description).size(12),
|
||||
text(details).size(12).color(muted_text()),
|
||||
text(source).size(11).color(muted_text()),
|
||||
]
|
||||
.spacing(5);
|
||||
if let Some(error) = &extension.last_error {
|
||||
row_content = row_content.push(
|
||||
text(format!("Last hook error: {error}"))
|
||||
.size(12)
|
||||
.style(iced::widget::text::danger),
|
||||
);
|
||||
}
|
||||
if self.pending_extension_trust.as_deref() == Some(extension.id.as_str()) {
|
||||
row_content = row_content.push(
|
||||
column![
|
||||
text("Trust this extension's command hooks?").size(13),
|
||||
text("Hooks run local programs with your user permissions. DS4Server isolates their environment, limits runtime and output, and never invokes a shell, but the installed code can still read or change files you can access.")
|
||||
.size(12),
|
||||
row![
|
||||
action_button("Cancel").on_press(Message::CancelExtensionTrust),
|
||||
action_button("Trust and enable")
|
||||
.on_press(Message::ConfirmExtensionTrust),
|
||||
]
|
||||
.spacing(8),
|
||||
]
|
||||
.spacing(7),
|
||||
);
|
||||
}
|
||||
if self.pending_extension_uninstall.as_deref() == Some(extension.id.as_str()) {
|
||||
row_content = row_content.push(
|
||||
column![
|
||||
text("Remove this extension and its stored session data?").size(13),
|
||||
row![
|
||||
action_button("Cancel").on_press(Message::CancelUninstallExtension),
|
||||
action_button("Uninstall").on_press(Message::ConfirmUninstallExtension),
|
||||
]
|
||||
.spacing(8),
|
||||
]
|
||||
.spacing(7),
|
||||
);
|
||||
}
|
||||
installed_extensions = installed_extensions
|
||||
.push(row_content)
|
||||
.push(rule::horizontal(1));
|
||||
}
|
||||
let mut extension_content = column![
|
||||
text("Install a portable Codex plugin from an HTTPS Git repository. Updates are manual and preserve the selected ref.")
|
||||
.size(12),
|
||||
text_input("https://example.com/owner/extension.git", &self.extension_source)
|
||||
.on_input(Message::PreferenceExtensionSourceChanged)
|
||||
.padding(9),
|
||||
row![
|
||||
text_input("Optional branch, tag, or commit", &self.extension_ref)
|
||||
.on_input(Message::PreferenceExtensionRefChanged)
|
||||
.padding(9)
|
||||
.width(Length::Fill),
|
||||
action_button("Install").on_press(Message::InstallExtension),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(Alignment::Center),
|
||||
installed_extensions,
|
||||
]
|
||||
.spacing(10);
|
||||
if let Some(label) = self.extension_operation_label() {
|
||||
extension_content = extension_content.push(text(format!("{label}…")).size(12));
|
||||
}
|
||||
if let Some(error) = &self.extension_error {
|
||||
extension_content =
|
||||
extension_content.push(text(error).size(12).style(iced::widget::text::danger));
|
||||
}
|
||||
let extension_group = preference_group(
|
||||
PreferenceSection::Extensions,
|
||||
"AGENT EXTENSIONS",
|
||||
extension_content,
|
||||
);
|
||||
let git_group = preference_group(
|
||||
PreferenceSection::Git,
|
||||
"GIT DIFFS",
|
||||
@@ -743,6 +859,7 @@ impl App {
|
||||
model_group,
|
||||
endpoint_group,
|
||||
dev_brain_group,
|
||||
extension_group,
|
||||
git_group,
|
||||
prompt_group,
|
||||
generation_group,
|
||||
|
||||
Reference in New Issue
Block a user