Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,42 @@ versioning when releases are cut.
and keep scheduled public runs inert so public publishing stays downstream of
the internal source-of-truth release.

## [0.10.74] - 2026-09-05

### Fixed

- Preserve exact model history when continuing summarized conversations, remove failed summary forks, retain message timestamps, and reject checkpoints that exceed the session format's size limit.
- Honor the selected high-contrast palette in conversation controls and Markdown.
- Release the current native TUI and managed default model through reviewed public source tags and signed native packages.

## [0.10.73] - 2026-09-06

### Added

- Enable tool-free Codex conversation summaries (#8449). <!-- maestro-release-note:eb4595ac1317 -->
- Stage and budget review proof experiments (#8450). <!-- maestro-release-note:f948b164eaff -->
- Submit reviewed bug reports to product feedback (#8445). <!-- maestro-release-note:91e0d93f66a2 -->
- Scope platform discovery to approved organizations (#8440). <!-- maestro-release-note:76841c522251 -->
- Recover drafts and selectively summarize conversations (#8437). <!-- maestro-release-note:83ab29392d38 -->

### Fixed

- Let proto-plan refresh moved SDK tags when preparing the base ref (#8447). <!-- maestro-release-note:d7da3c9ea208 -->
- Let a provider-routed credential read the operations it enqueued (#8438). <!-- maestro-release-note:ae6c7087190e -->
- Migrate outbound callers to scoped workload credentials (#8436). <!-- maestro-release-note:9b402f41b9c9 -->
- Migrate production service-token callers to workload credentials (#8441). <!-- maestro-release-note:547d5891e074 -->
- Give the stability fence the evalops/k8s token (#8444). <!-- maestro-release-note:c76682d322bf -->
- Narrow identity oauth invalid grant helper (#8439). <!-- maestro-release-note:4587c18473c1 -->
- Check prepaid model availability without reservations (#8427). <!-- maestro-release-note:b12963b7738a -->
- Recover missed credit funding after provider reconciliation (#8432). <!-- maestro-release-note:839cf26161ce -->

## [0.10.72] - 2026-09-05

### Fixed

- Preserve disabled Code device enrollment during signed release packaging. Enabled helpers remain required and checksum authenticated.
- Resolve release setup actions from Mono and retain mandatory native signing, notarization, and conformance checks.

## [0.10.71] - 2026-09-02

### Added
Expand Down
4 changes: 3 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ members = [
]

[workspace.dependencies]
textwrap = "0.16"
anyhow = "1"
http = "1.4"
aws-config = { version = "1.10.1", default-features = false, features = [
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@evalops/deixic-code",
"description": "Deixic Code — native Rust coding agent, CLI, TUI, and web runtime gateway",
"version": "0.10.71",
"version": "0.10.74",
"private": false,
"type": "module",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion packages/maestro-rs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "maestro"
version = "0.10.71"
version = "0.10.74"
edition = "2021"
license = "MIT"
description = "Canonical native Rust CLI for Deixic Code"
Expand Down
40 changes: 40 additions & 0 deletions packages/presentation-rs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Shared Dex Code presentation

The native TUI and lightweight workbench use these same renderers. Inputs are
borrowed presentation values; the application keeps execution, approvals, queue
semantics, preferences, and persistence.

## Composer

`components::composer::Composer` borrows the existing `maestro_ui::textarea::TextArea`,
preformatted queue rows, completion text, runtime footer, and a `UiTheme`.
Use `cursor_pos(area)` to place the terminal cursor. Rendering uses that same
viewport and keeps the cursor's wrapped row visible when the terminal shrinks.
Queue previews reserve an editor row and disclose clipping.

The existing TUI textarea path re-exports this editor for compatibility. Its
paste folding preserves original submitted bytes. Callers using `TextAreaWidget`
directly can supply a wrapped-row offset with `scroll(rows)`.

## Tool result

`components::tool_result::ToolResult` receives a typed `ToolPhase`, summary,
arguments, bounded output, optional truncation notice, and explicit expansion.
The native adapter remains responsible for tool summaries and output limits.
Execution identities appear in expanded details. Compact results show five
content rows and a remaining-line count; expanded results preserve blank rows
and show up to fifty output rows. Upstream truncation remains visible.

Use `lines(width)` for transcript composition or `height(width)` and `Widget`
for direct rendering. Measurement and rendering share the same layout.
Never derive success or permission from output text or a visual style.

## Adding a state

1. Reuse a production widget and pass values from the existing application owner.
2. Add a named example to `ui-preview-rs/src/conversation.rs` for each relevant state.
3. Test observable boundaries: cursor visibility, clipping, output disclosure, and status.
4. Run the focused preview command, then the full catalog and native capture cases.

`cargo test -p maestro-presentation -p maestro-ui -p maestro-ui-preview --locked`
runs the lightweight component tests. The workbench README has gallery commands.
151 changes: 151 additions & 0 deletions packages/presentation-rs/src/components/composer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//! Shared composer. Editing and queue effects remain with the caller.
use maestro_ui::{
UiTheme,
textarea::{TextArea, TextAreaWidget},
};
use ratatui::{
buffer::Buffer,
layout::{Alignment, Rect},
style::Style,
text::Line,
widgets::{Block, Borders, Paragraph, Widget},
};

pub const PROMPT_WIDTH: u16 = 2;

/// Borrow the real editor and preformatted queue rows, rather than copying state.
pub struct Composer<'a> {
pub editor: &'a TextArea,
pub queued: &'a [Line<'static>],
pub busy: bool,
pub footer: Option<&'a str>,
pub completion: Option<&'a str>,
pub theme: UiTheme,
}

impl Composer<'_> {
fn inner(area: Rect) -> Rect {
Rect::new(
area.x.saturating_add(1),
area.y.saturating_add(1),
area.width.saturating_sub(2),
area.height.saturating_sub(2),
)
}

fn queue_height(&self, area: Rect) -> u16 {
(self.queued.len().min(u16::MAX as usize) as u16)
.min(Self::inner(area).height.saturating_sub(1))
}

/// The exact viewport used by rendering and terminal cursor placement.
pub fn editor_area(&self, area: Rect) -> Rect {
let inner = Self::inner(area);
let queued = self.queue_height(area);
Rect::new(
inner.x.saturating_add(PROMPT_WIDTH),
inner.y.saturating_add(queued),
inner.width.saturating_sub(PROMPT_WIDTH),
inner.height.saturating_sub(queued),
)
}

pub fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {
if area.width < 3 || area.height < 3 {
return None;
}
let editor = self.editor_area(area);
if self.editor.is_empty() {
let inner = Self::inner(area);
return Some((
inner
.x
.saturating_add(PROMPT_WIDTH - 1)
.min(area.right() - 1),
editor.y,
));
}
if editor.is_empty() {
return None;
}
let (row, col) = self.editor.cursor_line_col(editor.width)?;
let scroll = row
.saturating_add(1)
.saturating_sub(usize::from(editor.height));
Some((
editor.x + col.min(editor.width - 1),
editor.y + (row - scroll) as u16,
))
}
}

impl Widget for Composer<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let area = area.intersection(buf.area);
if area.is_empty() {
return;
}
let theme = self.theme.on_panel();
Block::default()
.borders(Borders::TOP)
.border_style(Style::default().fg(if self.busy { theme.muted } else { theme.border }))
.style(Style::default().bg(theme.surface))
.render(area, buf);
if area.height < 3 || area.width < 3 {
return;
}
let inner = Self::inner(area);
let queued = self.queue_height(area);
if queued > 0 {
let mut lines = self.queued[..usize::from(queued)].to_vec();
if self.queued.len() > usize::from(queued) {
lines[usize::from(queued) - 1] =
Line::styled("… more queued input", Style::default().fg(theme.muted));
}
Paragraph::new(lines).render(
Rect {
height: queued,
..inner
},
buf,
);
}
let editor = self.editor_area(area);
buf.set_stringn(
inner.x,
editor.y,
"> ",
usize::from(inner.width),
Style::default().fg(theme.focus),
);
if !editor.is_empty() {
let scroll = self
.editor
.cursor_line_col(editor.width)
.map_or(0, |(row, _)| {
row.saturating_add(1)
.saturating_sub(usize::from(editor.height))
});
TextAreaWidget::new(self.editor)
.scroll(scroll)
.style(Style::default().fg(theme.text))
.render(editor, buf);
if let (Some(completion), Some((x, y))) = (self.completion, self.cursor_pos(area)) {
Comment thread
haasonsaas marked this conversation as resolved.
let x = x.max(editor.x);
buf.set_stringn(
x,
y,
completion,
usize::from(editor.right().saturating_sub(x)),
Style::default().fg(theme.muted),
);
}
}
if let Some(footer) = self.footer {
Paragraph::new(footer)
.style(Style::default().fg(theme.muted))
.alignment(Alignment::Right)
.render(Rect::new(inner.x, area.bottom() - 1, inner.width, 1), buf);
}
}
}
49 changes: 42 additions & 7 deletions packages/presentation-rs/src/components/deixic_logo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,19 @@ pub fn render_welcome_with_summary(
session_id: Option<&str>,
ready: bool,
facts: Option<(&str, &str)>,
) {
render_welcome_with_theme(area, buf, animate, session_id, ready, facts, None);
}

/// Render the same welcome layout using an explicitly supplied application palette.
pub fn render_welcome_with_theme(
area: Rect,
buf: &mut Buffer,
animate: bool,
session_id: Option<&str>,
ready: bool,
facts: Option<(&str, &str)>,
theme: Option<maestro_ui::UiTheme>,
) {
if area.is_empty() {
return;
Expand All @@ -317,7 +330,7 @@ pub fn render_welcome_with_summary(
LaunchState::Working
},
);
let summary = [
let mut summary = [
product_title_line(false).alignment(Alignment::Left),
facts.map_or_else(
|| hint_line().alignment(Alignment::Left),
Expand Down Expand Up @@ -362,20 +375,34 @@ pub fn render_welcome_with_summary(
},
),
];
if let Some(theme) = theme {
for (row, line) in summary.iter_mut().enumerate() {
let color = if row == 0 { theme.text } else { theme.muted };
line.style = line.style.fg(color);
for span in &mut line.spans {
span.style = span.style.fg(color);
}
}
}
if ready && facts.is_some() && area.height >= 6 {
Paragraph::new("What are we making?")
.style(Style::default().fg(Color::Rgb(
DEIXIC_MUTED.0,
DEIXIC_MUTED.1,
DEIXIC_MUTED.2,
.style(Style::default().fg(theme.map_or(
Color::Rgb(DEIXIC_MUTED.0, DEIXIC_MUTED.1, DEIXIC_MUTED.2),
|theme| theme.muted,
)))
.render(
Rect::new(area.x + 3, area.y + 5, area.width.saturating_sub(3), 1),
buf,
);
}
let logo_width = logo_visual_width(COMPACT_MIN_HEIGHT) + 3;
for (row, line) in logo.into_iter().enumerate() {
for (row, mut line) in logo.into_iter().enumerate() {
if let Some(theme) = theme {
line.style = line.style.fg(theme.focus);
for span in &mut line.spans {
span.style = span.style.fg(theme.focus);
}
}
Paragraph::new(line).render(
Rect::new(area.x + 1, area.y + 1 + row as u16, logo_width, 1),
buf,
Expand All @@ -394,7 +421,15 @@ pub fn render_welcome_with_summary(
}
return;
}
let content = welcome_content_lines_with_metadata(area.height, animate, session_id, ready);
let mut content = welcome_content_lines_with_metadata(area.height, animate, session_id, ready);
if let Some(theme) = theme {
for line in &mut content {
line.style = line.style.fg(theme.text);
for span in &mut line.spans {
span.style = span.style.fg(theme.text);
}
}
}
let content_height = content.len() as u16;
let y_offset = if area.height > content_height {
(area.height - content_height) / 2
Expand Down
Loading
Loading