fix: remove panel companion progress feed
This commit is contained in:
parent
2a7c96909c
commit
7ff2f8e3e8
|
|
@ -51,11 +51,6 @@ use crate::workspace_panel::{
|
||||||
|
|
||||||
const MAX_ENTRIES: usize = 50;
|
const MAX_ENTRIES: usize = 50;
|
||||||
const CLOSED_VISIBLE_ROWS: usize = 3;
|
const CLOSED_VISIBLE_ROWS: usize = 3;
|
||||||
const COMPANION_PROGRESS_MAX_TICKETS: usize = 5;
|
|
||||||
const COMPANION_PROGRESS_MAX_TITLE_CHARS: usize = 80;
|
|
||||||
const COMPANION_PROGRESS_MAX_MESSAGE_CHARS: usize = 1_800;
|
|
||||||
const COMPANION_PROGRESS_NOTICE_TEMPLATE: &str =
|
|
||||||
include_str!("../../../resources/prompts/panel/companion_progress_notice.md");
|
|
||||||
const ORCHESTRATOR_IDLE_QUEUE_NOTICE_TEMPLATE: &str =
|
const ORCHESTRATOR_IDLE_QUEUE_NOTICE_TEMPLATE: &str =
|
||||||
include_str!("../../../resources/prompts/panel/orchestrator_idle_queue_notice.md");
|
include_str!("../../../resources/prompts/panel/orchestrator_idle_queue_notice.md");
|
||||||
const ORCHESTRATOR_QUEUE_ATTENTION_MAX_TICKETS: usize = 6;
|
const ORCHESTRATOR_QUEUE_ATTENTION_MAX_TICKETS: usize = 6;
|
||||||
|
|
@ -142,10 +137,6 @@ pub(crate) async fn run(
|
||||||
let result = dispatch_orchestrator_queue_attention_notice(request).await;
|
let result = dispatch_orchestrator_queue_attention_notice(request).await;
|
||||||
app.finish_orchestrator_queue_attention_notice(result);
|
app.finish_orchestrator_queue_attention_notice(result);
|
||||||
}
|
}
|
||||||
if let Some(request) = app.prepare_companion_progress_notice() {
|
|
||||||
let result = dispatch_companion_progress_notice(request).await;
|
|
||||||
app.finish_companion_progress_notice(result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal.draw(|f| draw(f, app))?;
|
terminal.draw(|f| draw(f, app))?;
|
||||||
|
|
@ -542,79 +533,6 @@ struct PanelDiagnostic {
|
||||||
details: String,
|
details: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
struct CompanionProgressFreshness {
|
|
||||||
fingerprint: String,
|
|
||||||
updated_at: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
struct CompanionProgressNotice {
|
|
||||||
message: String,
|
|
||||||
fingerprint: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
struct CompanionProgressNoticeRequest {
|
|
||||||
pod_name: String,
|
|
||||||
socket_path: PathBuf,
|
|
||||||
notice: CompanionProgressNotice,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
struct CompanionProgressNoticeResult {
|
|
||||||
fingerprint: String,
|
|
||||||
updated_at: String,
|
|
||||||
error: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CompanionProgressNoticeResult {
|
|
||||||
fn sent(fingerprint: String, updated_at: String) -> Self {
|
|
||||||
Self {
|
|
||||||
fingerprint,
|
|
||||||
updated_at,
|
|
||||||
error: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn failed(fingerprint: String, error: impl Into<String>) -> Self {
|
|
||||||
Self {
|
|
||||||
fingerprint,
|
|
||||||
updated_at: String::new(),
|
|
||||||
error: Some(error.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
struct CompanionProgressTemplateContext {
|
|
||||||
companion: CompanionProgressTemplateRole,
|
|
||||||
orchestrator: CompanionProgressTemplateRole,
|
|
||||||
tickets: Vec<CompanionProgressTemplateTicket>,
|
|
||||||
omitted_ticket_count: usize,
|
|
||||||
role_pods: Vec<CompanionProgressTemplateRolePod>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
struct CompanionProgressTemplateRole {
|
|
||||||
pod_name: String,
|
|
||||||
status: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
struct CompanionProgressTemplateTicket {
|
|
||||||
id: String,
|
|
||||||
state: String,
|
|
||||||
title: String,
|
|
||||||
reference: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
struct CompanionProgressTemplateRolePod {
|
|
||||||
name: String,
|
|
||||||
status: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
struct OrchestratorWorkSet {
|
struct OrchestratorWorkSet {
|
||||||
active_inprogress: Vec<OrchestratorActiveWorkItem>,
|
active_inprogress: Vec<OrchestratorActiveWorkItem>,
|
||||||
|
|
@ -748,7 +666,6 @@ pub(crate) struct MultiPodApp {
|
||||||
runtime_command: PodRuntimeCommand,
|
runtime_command: PodRuntimeCommand,
|
||||||
last_companion_lifecycle_failure: Option<CompanionPanelState>,
|
last_companion_lifecycle_failure: Option<CompanionPanelState>,
|
||||||
last_orchestrator_lifecycle_failure: Option<OrchestratorPanelState>,
|
last_orchestrator_lifecycle_failure: Option<OrchestratorPanelState>,
|
||||||
companion_progress: Option<CompanionProgressFreshness>,
|
|
||||||
orchestrator_work_set: OrchestratorWorkSet,
|
orchestrator_work_set: OrchestratorWorkSet,
|
||||||
orchestrator_queue_attention: Option<OrchestratorQueueAttentionFreshness>,
|
orchestrator_queue_attention: Option<OrchestratorQueueAttentionFreshness>,
|
||||||
}
|
}
|
||||||
|
|
@ -784,7 +701,6 @@ impl MultiPodApp {
|
||||||
runtime_command,
|
runtime_command,
|
||||||
last_companion_lifecycle_failure: None,
|
last_companion_lifecycle_failure: None,
|
||||||
last_orchestrator_lifecycle_failure: None,
|
last_orchestrator_lifecycle_failure: None,
|
||||||
companion_progress: None,
|
|
||||||
orchestrator_work_set: OrchestratorWorkSet::default(),
|
orchestrator_work_set: OrchestratorWorkSet::default(),
|
||||||
orchestrator_queue_attention: None,
|
orchestrator_queue_attention: None,
|
||||||
}
|
}
|
||||||
|
|
@ -839,7 +755,6 @@ impl MultiPodApp {
|
||||||
self.ensure_composer_target_available();
|
self.ensure_composer_target_available();
|
||||||
self.refresh_orchestrator_work_set();
|
self.refresh_orchestrator_work_set();
|
||||||
self.apply_orchestrator_work_set_detail();
|
self.apply_orchestrator_work_set_detail();
|
||||||
self.apply_companion_progress_freshness();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_orchestrator_queue_attention_notice(
|
fn prepare_orchestrator_queue_attention_notice(
|
||||||
|
|
@ -895,49 +810,6 @@ impl MultiPodApp {
|
||||||
apply_orchestrator_detail(&mut self.panel, detail);
|
apply_orchestrator_detail(&mut self.panel, detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_companion_progress_notice(&mut self) -> Option<CompanionProgressNoticeRequest> {
|
|
||||||
let target = companion_progress_notice_target(&self.panel, &self.list)?;
|
|
||||||
let notice = companion_progress_notice(&self.panel, &self.list)?;
|
|
||||||
if self
|
|
||||||
.companion_progress
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|freshness| freshness.fingerprint == notice.fingerprint)
|
|
||||||
{
|
|
||||||
self.apply_companion_progress_freshness();
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(CompanionProgressNoticeRequest {
|
|
||||||
pod_name: target.pod_name,
|
|
||||||
socket_path: target.socket_path,
|
|
||||||
notice,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish_companion_progress_notice(&mut self, result: CompanionProgressNoticeResult) {
|
|
||||||
if let Some(error) = result.error {
|
|
||||||
self.notice = Some(format!("Companion progress notice not delivered: {error}"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.companion_progress = Some(CompanionProgressFreshness {
|
|
||||||
fingerprint: result.fingerprint,
|
|
||||||
updated_at: result.updated_at,
|
|
||||||
});
|
|
||||||
self.apply_companion_progress_freshness();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_companion_progress_freshness(&mut self) {
|
|
||||||
let Some(freshness) = self.companion_progress.as_ref() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(companion) = self.panel.header.companion.as_mut() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
companion.detail = Some(format!(
|
|
||||||
"progress context updated at {} (weak notify)",
|
|
||||||
freshness.updated_at
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_companion_lifecycle_memory(&mut self, panel: &mut WorkspacePanelViewModel) {
|
fn apply_companion_lifecycle_memory(&mut self, panel: &mut WorkspacePanelViewModel) {
|
||||||
let Some(state) = panel.header.companion.as_ref() else {
|
let Some(state) = panel.header.companion.as_ref() else {
|
||||||
self.last_companion_lifecycle_failure = None;
|
self.last_companion_lifecycle_failure = None;
|
||||||
|
|
@ -2910,123 +2782,6 @@ fn apply_orchestrator_detail(panel: &mut WorkspacePanelViewModel, detail: Option
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
struct CompanionProgressNoticeTarget {
|
|
||||||
pod_name: String,
|
|
||||||
socket_path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn companion_progress_notice_target(
|
|
||||||
panel: &WorkspacePanelViewModel,
|
|
||||||
list: &PodList,
|
|
||||||
) -> Option<CompanionProgressNoticeTarget> {
|
|
||||||
let companion = panel.header.companion.as_ref()?;
|
|
||||||
if !companion_status_is_peer_reachable(companion.status) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let entry = list
|
|
||||||
.entries
|
|
||||||
.iter()
|
|
||||||
.find(|entry| entry.name == companion.pod_name)?;
|
|
||||||
let live = entry.live.as_ref()?;
|
|
||||||
if !live.reachable {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(CompanionProgressNoticeTarget {
|
|
||||||
pod_name: companion.pod_name.clone(),
|
|
||||||
socket_path: live.socket_path.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn companion_status_is_peer_reachable(status: CompanionPanelStatus) -> bool {
|
|
||||||
matches!(
|
|
||||||
status,
|
|
||||||
CompanionPanelStatus::Live | CompanionPanelStatus::Restored | CompanionPanelStatus::Spawned
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn companion_progress_notice(
|
|
||||||
panel: &WorkspacePanelViewModel,
|
|
||||||
list: &PodList,
|
|
||||||
) -> Option<CompanionProgressNotice> {
|
|
||||||
let companion = panel.header.companion.as_ref()?;
|
|
||||||
let orchestrator = panel.header.orchestrator.as_ref()?;
|
|
||||||
let ticket_rows = panel
|
|
||||||
.rows
|
|
||||||
.iter()
|
|
||||||
.filter_map(|row| row.ticket.as_ref().map(|ticket| (row, ticket)))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let tickets = ticket_rows
|
|
||||||
.iter()
|
|
||||||
.take(COMPANION_PROGRESS_MAX_TICKETS)
|
|
||||||
.map(|(row, ticket)| CompanionProgressTemplateTicket {
|
|
||||||
id: bounded_progress_text(&ticket.id, COMPANION_PROGRESS_MAX_TITLE_CHARS),
|
|
||||||
state: bounded_progress_text(&row.status, COMPANION_PROGRESS_MAX_TITLE_CHARS),
|
|
||||||
title: bounded_progress_text(&row.title, COMPANION_PROGRESS_MAX_TITLE_CHARS),
|
|
||||||
reference: format!(".yoi/tickets/{}", ticket.id),
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let context = CompanionProgressTemplateContext {
|
|
||||||
companion: CompanionProgressTemplateRole {
|
|
||||||
pod_name: bounded_progress_text(
|
|
||||||
&companion.pod_name,
|
|
||||||
COMPANION_PROGRESS_MAX_TITLE_CHARS,
|
|
||||||
),
|
|
||||||
status: companion.status.label().to_string(),
|
|
||||||
},
|
|
||||||
orchestrator: CompanionProgressTemplateRole {
|
|
||||||
pod_name: bounded_progress_text(
|
|
||||||
&orchestrator.pod_name,
|
|
||||||
COMPANION_PROGRESS_MAX_TITLE_CHARS,
|
|
||||||
),
|
|
||||||
status: orchestrator.status.label().to_string(),
|
|
||||||
},
|
|
||||||
tickets,
|
|
||||||
omitted_ticket_count: ticket_rows
|
|
||||||
.len()
|
|
||||||
.saturating_sub(COMPANION_PROGRESS_MAX_TICKETS),
|
|
||||||
role_pods: bounded_role_pod_values(list, companion, orchestrator),
|
|
||||||
};
|
|
||||||
let rendered = render_companion_progress_notice_template(&context).ok()?;
|
|
||||||
let message = bounded_progress_text(&rendered, COMPANION_PROGRESS_MAX_MESSAGE_CHARS);
|
|
||||||
let fingerprint = message.clone();
|
|
||||||
Some(CompanionProgressNotice {
|
|
||||||
message,
|
|
||||||
fingerprint,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_companion_progress_notice_template(
|
|
||||||
context: &CompanionProgressTemplateContext,
|
|
||||||
) -> Result<String, minijinja::Error> {
|
|
||||||
let mut env = minijinja::Environment::new();
|
|
||||||
env.set_undefined_behavior(minijinja::UndefinedBehavior::Strict);
|
|
||||||
env.add_template(
|
|
||||||
"companion_progress_notice",
|
|
||||||
COMPANION_PROGRESS_NOTICE_TEMPLATE,
|
|
||||||
)?;
|
|
||||||
env.get_template("companion_progress_notice")?
|
|
||||||
.render(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bounded_role_pod_values(
|
|
||||||
list: &PodList,
|
|
||||||
companion: &CompanionPanelState,
|
|
||||||
orchestrator: &OrchestratorPanelState,
|
|
||||||
) -> Vec<CompanionProgressTemplateRolePod> {
|
|
||||||
let mut role_pods = Vec::new();
|
|
||||||
for name in [&companion.pod_name, &orchestrator.pod_name] {
|
|
||||||
let Some(entry) = list.entries.iter().find(|entry| entry.name == *name) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
role_pods.push(CompanionProgressTemplateRolePod {
|
|
||||||
name: bounded_progress_text(&entry.name, COMPANION_PROGRESS_MAX_TITLE_CHARS),
|
|
||||||
status: row_status_label(entry).0.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
role_pods
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bounded_progress_text(input: &str, max_chars: usize) -> String {
|
fn bounded_progress_text(input: &str, max_chars: usize) -> String {
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
for (idx, ch) in input.chars().enumerate() {
|
for (idx, ch) in input.chars().enumerate() {
|
||||||
|
|
@ -3051,19 +2806,6 @@ fn progress_notice_timestamp() -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn dispatch_companion_progress_notice(
|
|
||||||
request: CompanionProgressNoticeRequest,
|
|
||||||
) -> CompanionProgressNoticeResult {
|
|
||||||
let fingerprint = request.notice.fingerprint.clone();
|
|
||||||
match send_notify_only(&request.socket_path, request.notice.message, false).await {
|
|
||||||
Ok(()) => CompanionProgressNoticeResult::sent(fingerprint, progress_notice_timestamp()),
|
|
||||||
Err(err) => CompanionProgressNoticeResult::failed(
|
|
||||||
fingerprint,
|
|
||||||
format!("{}: {}", request.pod_name, err),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn dispatch_orchestrator_queue_attention_notice(
|
async fn dispatch_orchestrator_queue_attention_notice(
|
||||||
request: OrchestratorQueueAttentionNoticeRequest,
|
request: OrchestratorQueueAttentionNoticeRequest,
|
||||||
) -> OrchestratorQueueAttentionNoticeResult {
|
) -> OrchestratorQueueAttentionNoticeResult {
|
||||||
|
|
@ -5492,175 +5234,6 @@ mod tests {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn companion_progress_notice_target_skips_missing_stopped_and_unreachable_without_spawn_restore()
|
|
||||||
{
|
|
||||||
let missing_app = ticket_enabled_app(vec![live_info("test-orchestrator", PodStatus::Idle)]);
|
|
||||||
assert!(companion_progress_notice_target(&missing_app.panel, &missing_app.list).is_none());
|
|
||||||
|
|
||||||
let mut stopped_panel = WorkspacePanelViewModel::empty(Path::new("test"));
|
|
||||||
stopped_panel.header.companion = Some(CompanionPanelState::new(
|
|
||||||
"yoi",
|
|
||||||
CompanionPanelStatus::Stopped,
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
stopped_panel.header.orchestrator = Some(OrchestratorPanelState::new(
|
|
||||||
"test-orchestrator",
|
|
||||||
OrchestratorPanelStatus::Live,
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
let stopped_list = PodList::from_sources(
|
|
||||||
PodVisibilitySource::ResumePicker,
|
|
||||||
vec![stopped_info("yoi")],
|
|
||||||
vec![live_info("test-orchestrator", PodStatus::Idle)],
|
|
||||||
None,
|
|
||||||
10,
|
|
||||||
);
|
|
||||||
assert!(companion_progress_notice_target(&stopped_panel, &stopped_list).is_none());
|
|
||||||
|
|
||||||
let mut unreachable = live_info("yoi", PodStatus::Idle);
|
|
||||||
unreachable.reachable = false;
|
|
||||||
let unreachable_app = ticket_enabled_app(vec![
|
|
||||||
unreachable,
|
|
||||||
live_info("test-orchestrator", PodStatus::Idle),
|
|
||||||
]);
|
|
||||||
assert!(
|
|
||||||
companion_progress_notice_target(&unreachable_app.panel, &unreachable_app.list)
|
|
||||||
.is_none()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn companion_progress_notice_uses_prompt_resource_template() {
|
|
||||||
let first_resource_line = COMPANION_PROGRESS_NOTICE_TEMPLATE.lines().next().unwrap();
|
|
||||||
let context = CompanionProgressTemplateContext {
|
|
||||||
companion: CompanionProgressTemplateRole {
|
|
||||||
pod_name: "yoi".to_string(),
|
|
||||||
status: "Live".to_string(),
|
|
||||||
},
|
|
||||||
orchestrator: CompanionProgressTemplateRole {
|
|
||||||
pod_name: "test-orchestrator".to_string(),
|
|
||||||
status: "Live".to_string(),
|
|
||||||
},
|
|
||||||
tickets: vec![CompanionProgressTemplateTicket {
|
|
||||||
id: "RESOURCE-TICKET".to_string(),
|
|
||||||
state: "inprogress".to_string(),
|
|
||||||
title: "Rendered from runtime values".to_string(),
|
|
||||||
reference: ".yoi/tickets/RESOURCE-TICKET".to_string(),
|
|
||||||
}],
|
|
||||||
omitted_ticket_count: 0,
|
|
||||||
role_pods: vec![CompanionProgressTemplateRolePod {
|
|
||||||
name: "yoi".to_string(),
|
|
||||||
status: "idle".to_string(),
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
let rendered = render_companion_progress_notice_template(&context).unwrap();
|
|
||||||
assert!(rendered.contains(first_resource_line));
|
|
||||||
assert!(rendered.contains("RESOURCE-TICKET"));
|
|
||||||
assert!(rendered.contains("Rendered from runtime values"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn companion_progress_notice_is_bounded_and_excludes_sensitive_unbounded_fields() {
|
|
||||||
let mut app = ticket_enabled_app(vec![
|
|
||||||
live_info("yoi", PodStatus::Idle),
|
|
||||||
live_info("test-orchestrator", PodStatus::Running),
|
|
||||||
]);
|
|
||||||
app.panel.rows = (0..12)
|
|
||||||
.map(|index| {
|
|
||||||
let mut row = panel_test_ticket_row(
|
|
||||||
&format!("TICKET-{index}"),
|
|
||||||
&format!("Visible title {index} {}", "x".repeat(140)),
|
|
||||||
ActionPriority::Background,
|
|
||||||
NextUserAction::Wait,
|
|
||||||
"inprogress",
|
|
||||||
);
|
|
||||||
if let Some(ticket) = row.ticket.as_mut() {
|
|
||||||
ticket.latest_event_excerpt = Some(
|
|
||||||
"SECRET_PROVIDER_ERROR_TOKEN should never be copied into progress notices"
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
row.subtitle = Some("private thread excerpt should stay out".to_string());
|
|
||||||
row
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
app.panel
|
|
||||||
.header
|
|
||||||
.diagnostics
|
|
||||||
.push("diagnostic with SECRET_PROVIDER_ERROR_TOKEN should stay out".to_string());
|
|
||||||
|
|
||||||
let notice = companion_progress_notice(&app.panel, &app.list).unwrap();
|
|
||||||
assert!(notice.message.contains("TICKET-0"));
|
|
||||||
assert!(notice.message.contains("ref: .yoi/tickets/TICKET-0"));
|
|
||||||
assert!(notice.message.contains("more ticket(s) omitted"));
|
|
||||||
assert!(notice.message.chars().count() <= COMPANION_PROGRESS_MAX_MESSAGE_CHARS + 1);
|
|
||||||
assert!(!notice.message.contains("SECRET_PROVIDER_ERROR_TOKEN"));
|
|
||||||
assert!(!notice.message.contains("private thread excerpt"));
|
|
||||||
assert_eq!(notice.fingerprint, notice.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn companion_progress_notice_success_sets_panel_freshness_without_persisting_snapshot() {
|
|
||||||
let mut app = ticket_enabled_app(vec![
|
|
||||||
live_info("yoi", PodStatus::Idle),
|
|
||||||
live_info("test-orchestrator", PodStatus::Idle),
|
|
||||||
]);
|
|
||||||
app.panel.rows.push(panel_test_ticket_row(
|
|
||||||
"TICKET-1",
|
|
||||||
"Implement progress notices",
|
|
||||||
ActionPriority::Background,
|
|
||||||
NextUserAction::Wait,
|
|
||||||
"inprogress",
|
|
||||||
));
|
|
||||||
|
|
||||||
let request = app.prepare_companion_progress_notice().unwrap();
|
|
||||||
assert_eq!(request.pod_name, "yoi");
|
|
||||||
app.finish_companion_progress_notice(CompanionProgressNoticeResult::sent(
|
|
||||||
request.notice.fingerprint,
|
|
||||||
"unix:42".to_string(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let detail = app
|
|
||||||
.panel
|
|
||||||
.header
|
|
||||||
.companion
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|companion| companion.detail.as_deref())
|
|
||||||
.unwrap();
|
|
||||||
assert!(detail.contains("unix:42"));
|
|
||||||
assert!(detail.contains("weak notify"));
|
|
||||||
assert!(app.prepare_companion_progress_notice().is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn companion_progress_notice_target_accepts_live_running_companion() {
|
|
||||||
let app = ticket_enabled_app(vec![
|
|
||||||
live_info("yoi", PodStatus::Running),
|
|
||||||
live_info("test-orchestrator", PodStatus::Running),
|
|
||||||
]);
|
|
||||||
let target = companion_progress_notice_target(&app.panel, &app.list).unwrap();
|
|
||||||
assert_eq!(target.pod_name, "yoi");
|
|
||||||
assert_eq!(target.socket_path, PathBuf::from("/tmp/yoi.sock"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn companion_progress_failure_is_best_effort_and_does_not_mark_freshness() {
|
|
||||||
let mut app = ticket_enabled_app(vec![
|
|
||||||
live_info("yoi", PodStatus::Idle),
|
|
||||||
live_info("test-orchestrator", PodStatus::Idle),
|
|
||||||
]);
|
|
||||||
let request = app.prepare_companion_progress_notice().unwrap();
|
|
||||||
app.finish_companion_progress_notice(CompanionProgressNoticeResult::failed(
|
|
||||||
request.notice.fingerprint,
|
|
||||||
"socket closed",
|
|
||||||
));
|
|
||||||
|
|
||||||
assert!(app.companion_progress.is_none());
|
|
||||||
assert!(app.notice.as_deref().unwrap().contains("not delivered"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_ticket_selection_keeps_enter_pod_centric() {
|
fn no_ticket_selection_keeps_enter_pod_centric() {
|
||||||
let mut app = test_app(vec![live_info("alpha", PodStatus::Idle)]);
|
let mut app = test_app(vec![live_info("alpha", PodStatus::Idle)]);
|
||||||
|
|
@ -7254,7 +6827,6 @@ mod tests {
|
||||||
runtime_command: PodRuntimeCommand::for_executable("/tmp/yoi"),
|
runtime_command: PodRuntimeCommand::for_executable("/tmp/yoi"),
|
||||||
last_companion_lifecycle_failure,
|
last_companion_lifecycle_failure,
|
||||||
last_orchestrator_lifecycle_failure,
|
last_orchestrator_lifecycle_failure,
|
||||||
companion_progress: None,
|
|
||||||
orchestrator_work_set: OrchestratorWorkSet::default(),
|
orchestrator_work_set: OrchestratorWorkSet::default(),
|
||||||
orchestrator_queue_attention: None,
|
orchestrator_queue_attention: None,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
Orchestrator progress context (read-only weak notification; no auto-run).
|
|
||||||
Reason: workspace Panel refreshed bounded orchestration progress for Companion explanation.
|
|
||||||
Roles: Companion {{ companion.pod_name }} is {{ companion.status }}; Orchestrator {{ orchestrator.pod_name }} is {{ orchestrator.status }}.
|
|
||||||
|
|
||||||
{% if tickets %}Tickets (first {{ tickets | length }} visible, bounded):
|
|
||||||
{% for ticket in tickets %}- {{ ticket.id }} [{{ ticket.state }}] {{ ticket.title }} (ref: {{ ticket.reference }})
|
|
||||||
{% endfor %}{% if omitted_ticket_count > 0 %}- … {{ omitted_ticket_count }} more ticket(s) omitted from this bounded notice.
|
|
||||||
{% endif %}{% else %}Tickets: none visible in the current Panel snapshot.
|
|
||||||
{% endif %}{% if role_pods %}
|
|
||||||
Role pod status snapshot:
|
|
||||||
{% for role_pod in role_pods %}- {{ role_pod.name }}: {{ role_pod.status }}
|
|
||||||
{% endfor %}{% endif %}
|
|
||||||
Loading…
Reference in New Issue
Block a user