fix: resource-back companion progress notice
This commit is contained in:
parent
724b79f1c0
commit
61e6c0683c
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -3962,6 +3962,7 @@ dependencies = [
|
||||||
"crossterm 0.28.1",
|
"crossterm 0.28.1",
|
||||||
"llm-worker",
|
"llm-worker",
|
||||||
"manifest",
|
"manifest",
|
||||||
|
"minijinja",
|
||||||
"pod-registry",
|
"pod-registry",
|
||||||
"pod-store",
|
"pod-store",
|
||||||
"protocol",
|
"protocol",
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ pod-registry = { workspace = true }
|
||||||
provider = { workspace = true }
|
provider = { workspace = true }
|
||||||
ticket = { workspace = true }
|
ticket = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
minijinja = "2.19.0"
|
||||||
pulldown-cmark = { version = "0.13.3", default-features = false }
|
pulldown-cmark = { version = "0.13.3", default-features = false }
|
||||||
llm-worker.workspace = true
|
llm-worker.workspace = true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ use ratatui::layout::{Constraint, Layout, Position, Rect};
|
||||||
use ratatui::style::{Color, Modifier, Style};
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
use ratatui::text::{Line, Span};
|
use ratatui::text::{Line, Span};
|
||||||
use ratatui::widgets::{Block, Borders, Clear, Paragraph, Widget, Wrap};
|
use ratatui::widgets::{Block, Borders, Clear, Paragraph, Widget, Wrap};
|
||||||
|
use serde::Serialize;
|
||||||
use session_store::FsStore;
|
use session_store::FsStore;
|
||||||
use ticket::config::TicketConfig;
|
use ticket::config::TicketConfig;
|
||||||
use ticket::{LocalTicketBackend, TicketBackend, TicketIdOrSlug, TicketWorkflowState};
|
use ticket::{LocalTicketBackend, TicketBackend, TicketIdOrSlug, TicketWorkflowState};
|
||||||
|
|
@ -52,6 +53,8 @@ const CLOSED_VISIBLE_ROWS: usize = 3;
|
||||||
const COMPANION_PROGRESS_MAX_TICKETS: usize = 5;
|
const COMPANION_PROGRESS_MAX_TICKETS: usize = 5;
|
||||||
const COMPANION_PROGRESS_MAX_TITLE_CHARS: usize = 80;
|
const COMPANION_PROGRESS_MAX_TITLE_CHARS: usize = 80;
|
||||||
const COMPANION_PROGRESS_MAX_MESSAGE_CHARS: usize = 1_800;
|
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 SOCKET_OP_TIMEOUT: Duration = Duration::from_secs(3);
|
const SOCKET_OP_TIMEOUT: Duration = Duration::from_secs(3);
|
||||||
const MULTI_POD_POLL_INTERVAL: Duration = Duration::from_millis(1_500);
|
const MULTI_POD_POLL_INTERVAL: Duration = Duration::from_millis(1_500);
|
||||||
const TERMINAL_EVENT_POLL_INTERVAL: Duration = Duration::from_millis(100);
|
const TERMINAL_EVENT_POLL_INTERVAL: Duration = Duration::from_millis(100);
|
||||||
|
|
@ -573,6 +576,35 @@ impl CompanionProgressNoticeResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct MultiPodApp {
|
pub(crate) struct MultiPodApp {
|
||||||
pub(crate) list: PodList,
|
pub(crate) list: PodList,
|
||||||
pub(crate) panel: WorkspacePanelViewModel,
|
pub(crate) panel: WorkspacePanelViewModel,
|
||||||
|
|
@ -2426,57 +2458,44 @@ fn companion_progress_notice(
|
||||||
) -> Option<CompanionProgressNotice> {
|
) -> Option<CompanionProgressNotice> {
|
||||||
let companion = panel.header.companion.as_ref()?;
|
let companion = panel.header.companion.as_ref()?;
|
||||||
let orchestrator = panel.header.orchestrator.as_ref()?;
|
let orchestrator = panel.header.orchestrator.as_ref()?;
|
||||||
let mut lines = vec![
|
let ticket_rows = panel
|
||||||
"Orchestrator progress context (read-only weak notification; no auto-run).".to_string(),
|
.rows
|
||||||
"Reason: workspace Panel refreshed bounded orchestration progress for Companion explanation."
|
.iter()
|
||||||
.to_string(),
|
.filter_map(|row| row.ticket.as_ref().map(|ticket| (row, ticket)))
|
||||||
format!(
|
.collect::<Vec<_>>();
|
||||||
"Roles: Companion {} is {}; Orchestrator {} is {}.",
|
let tickets = ticket_rows
|
||||||
companion.pod_name,
|
.iter()
|
||||||
companion.status.label(),
|
.take(COMPANION_PROGRESS_MAX_TICKETS)
|
||||||
orchestrator.pod_name,
|
.map(|(row, ticket)| CompanionProgressTemplateTicket {
|
||||||
orchestrator.status.label()
|
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(),
|
||||||
|
},
|
||||||
let mut ticket_lines = Vec::new();
|
orchestrator: CompanionProgressTemplateRole {
|
||||||
for row in panel.rows.iter().take(COMPANION_PROGRESS_MAX_TICKETS) {
|
pod_name: bounded_progress_text(
|
||||||
let ticket_id = row
|
&orchestrator.pod_name,
|
||||||
.ticket
|
COMPANION_PROGRESS_MAX_TITLE_CHARS,
|
||||||
.as_ref()
|
),
|
||||||
.map(|ticket| ticket.id.as_str())
|
status: orchestrator.status.label().to_string(),
|
||||||
.unwrap_or("unknown-ticket");
|
},
|
||||||
ticket_lines.push(format!(
|
tickets,
|
||||||
"- {} [{}] {} (ref: .yoi/tickets/{})",
|
omitted_ticket_count: ticket_rows
|
||||||
ticket_id,
|
.len()
|
||||||
row.status,
|
.saturating_sub(COMPANION_PROGRESS_MAX_TICKETS),
|
||||||
bounded_progress_text(&row.title, COMPANION_PROGRESS_MAX_TITLE_CHARS),
|
role_pods: bounded_role_pod_values(list, companion, orchestrator),
|
||||||
ticket_id
|
};
|
||||||
));
|
let rendered = render_companion_progress_notice_template(&context).ok()?;
|
||||||
}
|
let message = bounded_progress_text(&rendered, COMPANION_PROGRESS_MAX_MESSAGE_CHARS);
|
||||||
if ticket_lines.is_empty() {
|
|
||||||
lines.push("Tickets: none visible in the current Panel snapshot.".to_string());
|
|
||||||
} else {
|
|
||||||
lines.push(format!(
|
|
||||||
"Tickets (first {} visible, bounded):",
|
|
||||||
ticket_lines.len()
|
|
||||||
));
|
|
||||||
lines.extend(ticket_lines);
|
|
||||||
if panel.rows.len() > COMPANION_PROGRESS_MAX_TICKETS {
|
|
||||||
lines.push(format!(
|
|
||||||
"- … {} more ticket(s) omitted from this bounded notice.",
|
|
||||||
panel.rows.len() - COMPANION_PROGRESS_MAX_TICKETS
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let role_pod_lines = bounded_role_pod_lines(list, companion, orchestrator);
|
|
||||||
if !role_pod_lines.is_empty() {
|
|
||||||
lines.push("Role pod status snapshot:".to_string());
|
|
||||||
lines.extend(role_pod_lines);
|
|
||||||
}
|
|
||||||
|
|
||||||
let message = bounded_progress_text(&lines.join("\n"), COMPANION_PROGRESS_MAX_MESSAGE_CHARS);
|
|
||||||
let fingerprint = message.clone();
|
let fingerprint = message.clone();
|
||||||
Some(CompanionProgressNotice {
|
Some(CompanionProgressNotice {
|
||||||
message,
|
message,
|
||||||
|
|
@ -2484,19 +2503,35 @@ fn companion_progress_notice(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounded_role_pod_lines(
|
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,
|
list: &PodList,
|
||||||
companion: &CompanionPanelState,
|
companion: &CompanionPanelState,
|
||||||
orchestrator: &OrchestratorPanelState,
|
orchestrator: &OrchestratorPanelState,
|
||||||
) -> Vec<String> {
|
) -> Vec<CompanionProgressTemplateRolePod> {
|
||||||
let mut lines = Vec::new();
|
let mut role_pods = Vec::new();
|
||||||
for name in [&companion.pod_name, &orchestrator.pod_name] {
|
for name in [&companion.pod_name, &orchestrator.pod_name] {
|
||||||
let Some(entry) = list.entries.iter().find(|entry| entry.name == *name) else {
|
let Some(entry) = list.entries.iter().find(|entry| entry.name == *name) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
lines.push(format!("- {}: {}", entry.name, row_status_label(entry).0));
|
role_pods.push(CompanionProgressTemplateRolePod {
|
||||||
|
name: bounded_progress_text(&entry.name, COMPANION_PROGRESS_MAX_TITLE_CHARS),
|
||||||
|
status: row_status_label(entry).0.to_string(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
lines
|
role_pods
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounded_progress_text(input: &str, max_chars: usize) -> String {
|
fn bounded_progress_text(input: &str, max_chars: usize) -> String {
|
||||||
|
|
@ -4987,6 +5022,37 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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]
|
#[test]
|
||||||
fn companion_progress_notice_is_bounded_and_excludes_sensitive_unbounded_fields() {
|
fn companion_progress_notice_is_bounded_and_excludes_sensitive_unbounded_fields() {
|
||||||
let mut app = ticket_enabled_app(vec![
|
let mut app = ticket_enabled_app(vec![
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ rustPlatform.buildRustPackage rec {
|
||||||
filter = sourceFilter;
|
filter = sourceFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
cargoHash = "sha256-WvMpHbTswYeRrkw5I4V4E1RnG7j13PbuQCbeas/XILs=";
|
cargoHash = "sha256-o47Erp9UrS2Rgwd0JNpuYPO4pZmv62DkzY9KXMQpyAM=";
|
||||||
|
|
||||||
depsExtraArgs = {
|
depsExtraArgs = {
|
||||||
# Older fetchCargoVendor utilities used crates.io's API download endpoint,
|
# Older fetchCargoVendor utilities used crates.io's API download endpoint,
|
||||||
|
|
|
||||||
12
resources/prompts/panel/companion_progress_notice.md
Normal file
12
resources/prompts/panel/companion_progress_notice.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
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