Compare commits
9 Commits
7880672737
...
372490707a
| Author | SHA1 | Date | |
|---|---|---|---|
| 372490707a | |||
| e7b89a169c | |||
| aab7af7e9c | |||
| 6e89d6017b | |||
| b8609c35b6 | |||
| d79b5d5cc4 | |||
| 775a4605cd | |||
| 96407899f7 | |||
| f73cfdc6e7 |
|
|
@ -507,6 +507,18 @@ mod tests {
|
||||||
assert!(rendered.contains("mark_read_required"));
|
assert!(rendered.contains("mark_read_required"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn internal_worker_prompts_do_not_include_default_memory_guidance() {
|
||||||
|
let cat = PromptCatalog::builtins_only().unwrap();
|
||||||
|
let compact = cat.compact_system().unwrap();
|
||||||
|
let extract = cat.memory_extract_system("Japanese").unwrap();
|
||||||
|
let consolidate = cat.memory_consolidation_system("Japanese").unwrap();
|
||||||
|
for rendered in [compact, extract, consolidate] {
|
||||||
|
assert!(!rendered.contains("### Memory and knowledge"));
|
||||||
|
assert!(!rendered.contains("Do not query memory every turn"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn memory_worker_prompts_include_language() {
|
fn memory_worker_prompts_include_language() {
|
||||||
let cat = PromptCatalog::builtins_only().unwrap();
|
let cat = PromptCatalog::builtins_only().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -440,7 +440,11 @@ mod tests {
|
||||||
let rendered = tmpl
|
let rendered = tmpl
|
||||||
.render(&ctx(dir.path(), &scope, vec!["Read".into()], None))
|
.render(&ctx(dir.path(), &scope, vec!["Read".into()], None))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// Builtin default body must expose the language policy.
|
// Builtin default body must expose the tool and language policies.
|
||||||
|
assert!(rendered.contains("### Memory and knowledge"));
|
||||||
|
assert!(rendered.contains("MemoryQuery"));
|
||||||
|
assert!(rendered.contains("MemoryRead(kind=summary)"));
|
||||||
|
assert!(rendered.contains("Do not query memory every turn"));
|
||||||
assert!(rendered.contains("## Language"));
|
assert!(rendered.contains("## Language"));
|
||||||
assert!(rendered.contains("`language`: `match the user's language"));
|
assert!(rendered.contains("`language`: `match the user's language"));
|
||||||
// Trailing section must be present.
|
// Trailing section must be present.
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ use crate::app::App;
|
||||||
use crate::picker::PickerOutcome;
|
use crate::picker::PickerOutcome;
|
||||||
use crate::spawn::{SpawnOutcome, SpawnReady};
|
use crate::spawn::{SpawnOutcome, SpawnReady};
|
||||||
|
|
||||||
|
type FullscreenTerminal = Terminal<CrosstermBackend<io::Stdout>>;
|
||||||
|
|
||||||
fn resolve_socket(pod_name: &str, override_path: Option<PathBuf>) -> PathBuf {
|
fn resolve_socket(pod_name: &str, override_path: Option<PathBuf>) -> PathBuf {
|
||||||
if let Some(p) = override_path {
|
if let Some(p) = override_path {
|
||||||
return p;
|
return p;
|
||||||
|
|
@ -289,33 +291,95 @@ async fn run_pod_name(
|
||||||
pod_name: String,
|
pod_name: String,
|
||||||
socket_override: Option<PathBuf>,
|
socket_override: Option<PathBuf>,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let preferred_socket = resolve_socket(&pod_name, socket_override.clone());
|
if let Some(client) = try_connect_live_pod(&pod_name, socket_override.clone()).await {
|
||||||
if let Some((_socket_path, client)) =
|
|
||||||
connect_live_pod(&pod_name, preferred_socket, socket_override.is_none()).await
|
|
||||||
{
|
|
||||||
let mut terminal = enter_fullscreen()?;
|
let mut terminal = enter_fullscreen()?;
|
||||||
let mut app = App::new(pod_name);
|
run_connected_pod(&mut terminal, pod_name, client).await?;
|
||||||
app.connected = true;
|
return Ok(());
|
||||||
return run_loop(&mut terminal, &mut app, client).await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ready = match spawn::run_pod_name(pod_name).await? {
|
let ready = match spawn::run_pod_name(pod_name).await? {
|
||||||
SpawnOutcome::Ready(r) => r,
|
SpawnOutcome::Ready(r) => r,
|
||||||
SpawnOutcome::Cancelled => return Ok(()),
|
SpawnOutcome::Cancelled => return Ok(()),
|
||||||
};
|
};
|
||||||
|
let mut terminal = enter_fullscreen()?;
|
||||||
|
terminal.clear()?;
|
||||||
|
let result = run_ready_pod(&mut terminal, ready).await;
|
||||||
|
let _ = leave_fullscreen(&mut terminal);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_connected_pod(
|
||||||
|
terminal: &mut FullscreenTerminal,
|
||||||
|
pod_name: String,
|
||||||
|
client: PodClient,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut app = App::new(pod_name);
|
||||||
|
app.connected = true;
|
||||||
|
run_loop(terminal, &mut app, client).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_pod_name_nested(
|
||||||
|
terminal: &mut FullscreenTerminal,
|
||||||
|
request: multi_pod::OpenPodRequest,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let multi_pod::OpenPodRequest {
|
||||||
|
pod_name,
|
||||||
|
socket_override,
|
||||||
|
} = request;
|
||||||
|
|
||||||
|
if let Some(client) = try_connect_live_pod(&pod_name, socket_override).await {
|
||||||
|
return run_connected_pod(terminal, pod_name, client).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ready = spawn_pod_name_from_fullscreen(terminal, &pod_name).await?;
|
||||||
|
run_ready_pod(terminal, ready).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn spawn_pod_name_from_fullscreen(
|
||||||
|
terminal: &mut FullscreenTerminal,
|
||||||
|
pod_name: &str,
|
||||||
|
) -> Result<SpawnReady, Box<dyn std::error::Error>> {
|
||||||
|
leave_fullscreen(terminal)?;
|
||||||
|
let outcome = spawn::run_pod_name(pod_name.to_string()).await;
|
||||||
|
enter_fullscreen_existing(terminal)?;
|
||||||
|
terminal.clear()?;
|
||||||
|
|
||||||
|
match outcome? {
|
||||||
|
SpawnOutcome::Ready(ready) => Ok(ready),
|
||||||
|
SpawnOutcome::Cancelled => Err(Box::new(NestedOpenCancelled)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_connect_live_pod(
|
||||||
|
pod_name: &str,
|
||||||
|
socket_override: Option<PathBuf>,
|
||||||
|
) -> Option<PodClient> {
|
||||||
|
let preferred_socket = resolve_socket(pod_name, socket_override.clone());
|
||||||
|
connect_live_pod(pod_name, preferred_socket, socket_override.is_none())
|
||||||
|
.await
|
||||||
|
.map(|(_, client)| client)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct NestedOpenCancelled;
|
||||||
|
|
||||||
|
impl std::fmt::Display for NestedOpenCancelled {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str("Pod open was cancelled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for NestedOpenCancelled {}
|
||||||
|
|
||||||
|
async fn run_ready_pod(
|
||||||
|
terminal: &mut FullscreenTerminal,
|
||||||
|
ready: SpawnReady,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let SpawnReady {
|
let SpawnReady {
|
||||||
pod_name,
|
pod_name,
|
||||||
socket_path,
|
socket_path,
|
||||||
} = ready;
|
} = ready;
|
||||||
|
run(terminal, pod_name, &socket_path).await
|
||||||
let mut terminal = enter_fullscreen()?;
|
|
||||||
let result = run(&mut terminal, pod_name, &socket_path).await;
|
|
||||||
let _ = execute!(
|
|
||||||
terminal.backend_mut(),
|
|
||||||
DisableMouseCapture,
|
|
||||||
LeaveAlternateScreen
|
|
||||||
);
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn connect_live_pod(
|
async fn connect_live_pod(
|
||||||
|
|
@ -354,24 +418,37 @@ async fn run_resume() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_multi() -> Result<(), Box<dyn std::error::Error>> {
|
async fn run_multi() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut app = multi_pod::load_app().await?;
|
||||||
let mut terminal = enter_fullscreen()?;
|
let mut terminal = enter_fullscreen()?;
|
||||||
let outcome = multi_pod::run(&mut terminal).await;
|
|
||||||
|
|
||||||
let _ = execute!(
|
loop {
|
||||||
terminal.backend_mut(),
|
match multi_pod::run(&mut terminal, &mut app).await? {
|
||||||
DisableMouseCapture,
|
multi_pod::MultiPodOutcome::Quit => {
|
||||||
LeaveAlternateScreen
|
let _ = leave_fullscreen(&mut terminal);
|
||||||
);
|
return Ok(());
|
||||||
|
}
|
||||||
match outcome? {
|
multi_pod::MultiPodOutcome::Open(request) => {
|
||||||
multi_pod::MultiPodOutcome::Quit => Ok(()),
|
let pod_name = request.pod_name.clone();
|
||||||
multi_pod::MultiPodOutcome::Open {
|
match run_pod_name_nested(&mut terminal, request).await {
|
||||||
pod_name,
|
Ok(()) => app.finish_open(&pod_name, Ok(())),
|
||||||
socket_override,
|
Err(error) if is_recoverable_multi_open_error(error.as_ref()) => {
|
||||||
} => run_pod_name(pod_name, socket_override).await,
|
app.finish_open(&pod_name, Err(error.as_ref()));
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
let _ = leave_fullscreen(&mut terminal);
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app.reload().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_recoverable_multi_open_error(error: &(dyn std::error::Error + 'static)) -> bool {
|
||||||
|
error.is::<spawn::SpawnError>() || error.is::<NestedOpenCancelled>()
|
||||||
|
}
|
||||||
|
|
||||||
async fn run_spawn(resume_from: Option<SegmentId>) -> Result<(), Box<dyn std::error::Error>> {
|
async fn run_spawn(resume_from: Option<SegmentId>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let ready = match spawn::run(resume_from).await? {
|
let ready = match spawn::run(resume_from).await? {
|
||||||
SpawnOutcome::Ready(r) => r,
|
SpawnOutcome::Ready(r) => r,
|
||||||
|
|
@ -396,16 +473,34 @@ async fn run_spawn(resume_from: Option<SegmentId>) -> Result<(), Box<dyn std::er
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter_fullscreen() -> Result<Terminal<CrosstermBackend<io::Stdout>>, Box<dyn std::error::Error>>
|
fn enter_fullscreen() -> Result<FullscreenTerminal, Box<dyn std::error::Error>> {
|
||||||
{
|
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||||
let backend = CrosstermBackend::new(stdout);
|
let backend = CrosstermBackend::new(stdout);
|
||||||
Ok(Terminal::new(backend)?)
|
Ok(Terminal::new(backend)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn enter_fullscreen_existing(
|
||||||
|
terminal: &mut FullscreenTerminal,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
execute!(
|
||||||
|
terminal.backend_mut(),
|
||||||
|
EnterAlternateScreen,
|
||||||
|
EnableMouseCapture
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn leave_fullscreen(terminal: &mut FullscreenTerminal) -> io::Result<()> {
|
||||||
|
execute!(
|
||||||
|
terminal.backend_mut(),
|
||||||
|
DisableMouseCapture,
|
||||||
|
LeaveAlternateScreen
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
terminal: &mut FullscreenTerminal,
|
||||||
pod_name: String,
|
pod_name: String,
|
||||||
socket_path: &std::path::Path,
|
socket_path: &std::path::Path,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
@ -438,7 +533,7 @@ const POD_EVENT_DRAIN_LIMIT: usize = 32;
|
||||||
|
|
||||||
struct TerminalEventReader {
|
struct TerminalEventReader {
|
||||||
stop: Arc<AtomicBool>,
|
stop: Arc<AtomicBool>,
|
||||||
_thread: thread::JoinHandle<()>,
|
thread: Option<thread::JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalEventReader {
|
impl TerminalEventReader {
|
||||||
|
|
@ -453,7 +548,7 @@ impl TerminalEventReader {
|
||||||
Ok((
|
Ok((
|
||||||
Self {
|
Self {
|
||||||
stop,
|
stop,
|
||||||
_thread: thread,
|
thread: Some(thread),
|
||||||
},
|
},
|
||||||
rx,
|
rx,
|
||||||
))
|
))
|
||||||
|
|
@ -463,6 +558,9 @@ impl TerminalEventReader {
|
||||||
impl Drop for TerminalEventReader {
|
impl Drop for TerminalEventReader {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.stop.store(true, Ordering::Relaxed);
|
self.stop.store(true, Ordering::Relaxed);
|
||||||
|
if let Some(thread) = self.thread.take() {
|
||||||
|
let _ = thread.join();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,37 +62,41 @@ impl From<session_store::StoreError> for MultiPodError {
|
||||||
|
|
||||||
pub(crate) enum MultiPodOutcome {
|
pub(crate) enum MultiPodOutcome {
|
||||||
Quit,
|
Quit,
|
||||||
Open {
|
Open(OpenPodRequest),
|
||||||
pod_name: String,
|
}
|
||||||
socket_override: Option<PathBuf>,
|
|
||||||
},
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub(crate) struct OpenPodRequest {
|
||||||
|
pub(crate) pod_name: String,
|
||||||
|
pub(crate) socket_override: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn load_app() -> Result<MultiPodApp, MultiPodError> {
|
||||||
|
MultiPodApp::load(None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run(
|
pub(crate) async fn run(
|
||||||
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
||||||
|
app: &mut MultiPodApp,
|
||||||
) -> Result<MultiPodOutcome, MultiPodError> {
|
) -> Result<MultiPodOutcome, MultiPodError> {
|
||||||
let mut app = MultiPodApp::load(None).await?;
|
|
||||||
if app.list.entries.is_empty() {
|
if app.list.entries.is_empty() {
|
||||||
return Err(MultiPodError::NoPods);
|
return Err(MultiPodError::NoPods);
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
terminal.draw(|f| draw(f, &mut app))?;
|
terminal.draw(|f| draw(f, app))?;
|
||||||
match read()? {
|
match read()? {
|
||||||
TermEvent::Key(key) => match app.handle_key(key) {
|
TermEvent::Key(key) => match app.handle_key(key) {
|
||||||
MultiPodAction::None => {}
|
MultiPodAction::None => {}
|
||||||
MultiPodAction::Quit => return Ok(MultiPodOutcome::Quit),
|
MultiPodAction::Quit => return Ok(MultiPodOutcome::Quit),
|
||||||
MultiPodAction::Open => {
|
MultiPodAction::Open => {
|
||||||
if let Some(entry) = app.list.selected_entry() {
|
if let Some(request) = app.prepare_open() {
|
||||||
return Ok(MultiPodOutcome::Open {
|
return Ok(MultiPodOutcome::Open(request));
|
||||||
pod_name: entry.name.clone(),
|
|
||||||
socket_override: entry.attach_socket_path().map(PathBuf::from),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MultiPodAction::Refresh => app.reload().await?,
|
MultiPodAction::Refresh => app.reload().await?,
|
||||||
MultiPodAction::Send(request) => {
|
MultiPodAction::Send(request) => {
|
||||||
terminal.draw(|f| draw(f, &mut app))?;
|
terminal.draw(|f| draw(f, app))?;
|
||||||
let result = send_run_and_confirm(&request.socket_path, request.segments).await;
|
let result = send_run_and_confirm(&request.socket_path, request.segments).await;
|
||||||
app.finish_send(result);
|
app.finish_send(result);
|
||||||
let _ = app.reload().await;
|
let _ = app.reload().await;
|
||||||
|
|
@ -147,7 +151,7 @@ impl MultiPodApp {
|
||||||
Ok(app)
|
Ok(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn reload(&mut self) -> Result<(), MultiPodError> {
|
pub(crate) async fn reload(&mut self) -> Result<(), MultiPodError> {
|
||||||
self.list = load_pod_list(self.list.selected_name.clone()).await?;
|
self.list = load_pod_list(self.list.selected_name.clone()).await?;
|
||||||
self.ensure_selection_visible();
|
self.ensure_selection_visible();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -211,6 +215,40 @@ impl MultiPodApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn prepare_open(&mut self) -> Option<OpenPodRequest> {
|
||||||
|
let entry = match self.list.selected_entry() {
|
||||||
|
Some(entry) => entry,
|
||||||
|
None => {
|
||||||
|
self.notice = Some("No Pod is selected.".to_string());
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if !entry.actions.can_open {
|
||||||
|
self.notice = Some("Selected Pod cannot be opened from this view.".to_string());
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.notice = Some(format!("Opening {}…", entry.name));
|
||||||
|
Some(OpenPodRequest {
|
||||||
|
pod_name: entry.name.clone(),
|
||||||
|
socket_override: entry.attach_socket_path().map(PathBuf::from),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn finish_open(
|
||||||
|
&mut self,
|
||||||
|
pod_name: &str,
|
||||||
|
result: Result<(), &dyn std::fmt::Display>,
|
||||||
|
) {
|
||||||
|
match result {
|
||||||
|
Ok(()) => {
|
||||||
|
self.notice = Some(format!("Returned from {pod_name}."));
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
self.notice = Some(format!("Open failed for {pod_name}: {error}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn prepare_send(&mut self) -> Option<DirectSendRequest> {
|
pub(crate) fn prepare_send(&mut self) -> Option<DirectSendRequest> {
|
||||||
let entry = match self.list.selected_entry() {
|
let entry = match self.list.selected_entry() {
|
||||||
Some(entry) => entry,
|
Some(entry) => entry,
|
||||||
|
|
@ -1114,6 +1152,53 @@ mod tests {
|
||||||
assert!(app.notice.as_deref().unwrap().contains("Delivered"));
|
assert!(app.notice.as_deref().unwrap().contains("Delivered"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_open_request_keeps_dashboard_state_for_nested_single_pod() {
|
||||||
|
let mut app = test_app(vec![live_info("alpha", PodStatus::Idle)]);
|
||||||
|
app.input.insert_str("draft survives open");
|
||||||
|
|
||||||
|
let request = app.prepare_open().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(request.pod_name, "alpha");
|
||||||
|
assert_eq!(
|
||||||
|
request.socket_override,
|
||||||
|
Some(PathBuf::from("/tmp/alpha.sock"))
|
||||||
|
);
|
||||||
|
assert_eq!(app.list.selected_entry().unwrap().name, "alpha");
|
||||||
|
assert_eq!(input_text(&app), "draft survives open");
|
||||||
|
assert!(app.notice.as_deref().unwrap().contains("Opening alpha"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_open_failure_keeps_composer_and_sets_notice() {
|
||||||
|
let mut app = test_app(vec![live_info("alpha", PodStatus::Idle)]);
|
||||||
|
app.input.insert_str("keep this draft");
|
||||||
|
let before = input_text(&app);
|
||||||
|
let error = io::Error::other("boom");
|
||||||
|
|
||||||
|
app.finish_open("alpha", Err(&error));
|
||||||
|
|
||||||
|
assert_eq!(input_text(&app), before);
|
||||||
|
assert_eq!(app.list.selected_entry().unwrap().name, "alpha");
|
||||||
|
assert!(
|
||||||
|
app.notice
|
||||||
|
.as_deref()
|
||||||
|
.unwrap()
|
||||||
|
.contains("Open failed for alpha")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_open_disabled_target_stays_in_dashboard() {
|
||||||
|
let mut live = live_info("unreachable", PodStatus::Idle);
|
||||||
|
live.reachable = false;
|
||||||
|
live.status = None;
|
||||||
|
let mut app = test_app(vec![live]);
|
||||||
|
|
||||||
|
assert!(app.prepare_open().is_none());
|
||||||
|
assert!(app.notice.as_deref().unwrap().contains("cannot be opened"));
|
||||||
|
}
|
||||||
|
|
||||||
fn test_app(live: Vec<LivePodInfo>) -> MultiPodApp {
|
fn test_app(live: Vec<LivePodInfo>) -> MultiPodApp {
|
||||||
app_with_list(PodList::from_sources(
|
app_with_list(PodList::from_sources(
|
||||||
PodVisibilitySource::ResumePicker,
|
PodVisibilitySource::ResumePicker,
|
||||||
|
|
|
||||||
25
package.nix
25
package.nix
|
|
@ -40,7 +40,30 @@ rustPlatform.buildRustPackage rec {
|
||||||
filter = sourceFilter;
|
filter = sourceFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
cargoLock.lockFile = ./Cargo.lock;
|
cargoHash = "sha256-8ZT5moKFxj/5vbp5rsUG7UkPLY1fvQKhYTyjRWQ58xk=";
|
||||||
|
|
||||||
|
depsExtraArgs = {
|
||||||
|
# nixpkgs 25.11's fetchCargoVendor still uses crates.io's API
|
||||||
|
# download endpoint in this environment, which returns 403 while the
|
||||||
|
# immutable static CDN endpoint works. Keep this local package build on
|
||||||
|
# static.crates.io until the upstream fetcher is fixed in our nixpkgs pin.
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
|
||||||
|
if [ -n "''${cargoRoot-}" ]; then
|
||||||
|
cd "$cargoRoot"
|
||||||
|
fi
|
||||||
|
|
||||||
|
vendor_util="$(command -v fetch-cargo-vendor-util-v2 || command -v fetch-cargo-vendor-util)"
|
||||||
|
cp "$vendor_util" ./fetch-cargo-vendor-util-static
|
||||||
|
substituteInPlace ./fetch-cargo-vendor-util-static \
|
||||||
|
--replace-fail 'https://crates.io/api/v1/crates/{pkg["name"]}/{pkg["version"]}/download' \
|
||||||
|
'https://static.crates.io/crates/{pkg["name"]}/{pkg["version"]}/download'
|
||||||
|
./fetch-cargo-vendor-util-static create-vendor-staging ./Cargo.lock "$out"
|
||||||
|
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
strictDeps = true;
|
strictDeps = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,10 @@ When searching, use grep/glob primitives rather than shell pipelines.
|
||||||
|
|
||||||
You can run multiple tools simultaneously by calling them within a single response.
|
You can run multiple tools simultaneously by calling them within a single response.
|
||||||
It is recommended to run tools that handle asynchronous processing, such as queries and readings, in batches.
|
It is recommended to run tools that handle asynchronous processing, such as queries and readings, in batches.
|
||||||
|
|
||||||
|
### Memory and knowledge
|
||||||
|
|
||||||
|
For past decisions, prior requests, durable preferences, project history, or why something was done, use targeted lookup instead of guessing from vague recollection.
|
||||||
|
Use `MemoryQuery` for durable memory records (summary, decisions, requests), `KnowledgeQuery` for project knowledge, `MemoryRead(kind=summary)` for the full memory summary, and `MemoryRead` on returned slugs when excerpts are insufficient.
|
||||||
|
Resident memory and knowledge are helpful context but may be stale; current user instructions, repository files, tickets, git history, and session logs are authoritative for exact current state.
|
||||||
|
Do not query memory every turn, and normally prefer read/query tools; use `MemoryWrite`, `MemoryEdit`, or `MemoryDelete` only when explicitly asked or in a memory maintenance worker.
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
id: 20260527-000005-memory-tool-guidance-prompt
|
id: 20260527-000005-memory-tool-guidance-prompt
|
||||||
slug: memory-tool-guidance-prompt
|
slug: memory-tool-guidance-prompt
|
||||||
title: プロンプト: memory / knowledge tool 利用タイミングのガイダンス
|
title: プロンプト: memory / knowledge tool 利用タイミングのガイダンス
|
||||||
status: open
|
status: closed
|
||||||
kind: task
|
kind: task
|
||||||
priority: P2
|
priority: P2
|
||||||
labels: [migrated]
|
labels: [migrated]
|
||||||
created_at: 2026-05-27T00:00:05Z
|
created_at: 2026-05-27T00:00:05Z
|
||||||
updated_at: 2026-05-27T00:00:05Z
|
updated_at: 2026-05-28T23:59:06Z
|
||||||
assignee: null
|
assignee: null
|
||||||
legacy_ticket: tickets/memory-tool-guidance-prompt.md
|
legacy_ticket: tickets/memory-tool-guidance-prompt.md
|
||||||
---
|
---
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
---
|
||||||
|
id: 20260527-000005-memory-tool-guidance-prompt
|
||||||
|
slug: memory-tool-guidance-prompt
|
||||||
|
title: プロンプト: memory / knowledge tool 利用タイミングのガイダンス
|
||||||
|
status: closed
|
||||||
|
kind: task
|
||||||
|
priority: P2
|
||||||
|
labels: [migrated]
|
||||||
|
created_at: 2026-05-27T00:00:05Z
|
||||||
|
updated_at: 2026-05-28T23:59:06Z
|
||||||
|
assignee: null
|
||||||
|
legacy_ticket: tickets/memory-tool-guidance-prompt.md
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration reference
|
||||||
|
|
||||||
|
- legacy_ticket: tickets/memory-tool-guidance-prompt.md
|
||||||
|
- migrated_from: TODO.md / tickets directory migration on 2026-05-27
|
||||||
|
|
||||||
|
# プロンプト: memory / knowledge tool 利用タイミングのガイダンス
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
通常 Pod には `MemoryQuery` / `MemoryRead` / `KnowledgeQuery` / `MemoryWrite` 等の memory / knowledge tools が提供されているが、現状の通常 system prompt はそれらを「いつ使うべきか」をほとんど説明していない。
|
||||||
|
|
||||||
|
現在の `resources/prompts/common/tool-usage.md` は、既知パスなら Read、検索なら Grep/Glob、並列可能ならまとめる、という汎用 tool 方針に留まる。memory / knowledge tools の description には操作方法はあるが、モデルが自発的に memory lookup すべき状況は明示されていない。
|
||||||
|
|
||||||
|
このため、過去の決定・ユーザー嗜好・以前の経緯を問われても、モデルが `MemoryQuery` / `MemoryRead` を自発的に使わない可能性が高い。`summary.md` resident injection により短い durable context は常時見えるようになるが、詳細な過去判断や request を探すには query guidance が必要である。
|
||||||
|
|
||||||
|
## 方針
|
||||||
|
|
||||||
|
通常 Pod の system prompt に、memory / knowledge tools の利用タイミングを短く追加する。
|
||||||
|
|
||||||
|
目的は「必要な時に過去情報を探す」ことであり、毎 turn memory query を強制することではない。memory / knowledge は helpful context だが stale になり得るため、現在の user instruction / files / tickets / git state / session log を上書きする権威として扱わせない。
|
||||||
|
|
||||||
|
## 推奨する追加文言
|
||||||
|
|
||||||
|
`resources/prompts/common/tool-usage.md` に新しい小節を足すか、`resources/prompts/common/memory.md` を作って `default.md` から include する。
|
||||||
|
|
||||||
|
例:
|
||||||
|
|
||||||
|
```md
|
||||||
|
## Memory and knowledge
|
||||||
|
|
||||||
|
Use memory and knowledge tools when the user asks about past decisions, prior requests, durable preferences, project history, or why something was done. Do not guess from vague recollection when a targeted memory lookup would answer the question.
|
||||||
|
|
||||||
|
- Use `MemoryQuery` for durable memory records: summary, decisions, and requests.
|
||||||
|
- Use `KnowledgeQuery` for project knowledge records.
|
||||||
|
- Use `MemoryRead(kind=summary)` when you need the full workspace memory summary.
|
||||||
|
- Use `MemoryRead` on returned slugs when query excerpts are insufficient.
|
||||||
|
|
||||||
|
Resident memory and knowledge are helpful context but may be stale. Current user instructions, repository files, tickets, git history, and session logs are more authoritative for exact current state.
|
||||||
|
|
||||||
|
Do not query memory on every turn. Prefer it when past context, user preferences, or prior rationale materially affects the answer or implementation.
|
||||||
|
```
|
||||||
|
|
||||||
|
文言は実装時に自然に調整してよいが、以下の意味は維持する。
|
||||||
|
|
||||||
|
- 過去判断 / 過去依頼 / ユーザー嗜好 / project history / why 系では memory lookup を促す。
|
||||||
|
- `MemoryQuery`, `KnowledgeQuery`, `MemoryRead(kind=summary)`, slug read の役割を明示する。
|
||||||
|
- resident context は stale になり得ると明示する。
|
||||||
|
- current user instruction / files / tickets / git / session logs の方が exact current state では強いと明示する。
|
||||||
|
- 毎 turn query しないと明示する。
|
||||||
|
|
||||||
|
## 要件
|
||||||
|
|
||||||
|
- 通常 Pod の default prompt に memory / knowledge tool 利用タイミングの guidance が入る。
|
||||||
|
- internal prompts (`memory_extract_system`, `memory_consolidation_system`, `compact_system`) の挙動を変えない。
|
||||||
|
- guidance は短く、通常 turn の token overhead を過度に増やさない。
|
||||||
|
- guidance は memory / knowledge を current authority より上に置かない。
|
||||||
|
- guidance は毎 turn memory query を促さない。
|
||||||
|
- `MemoryWrite` / `MemoryEdit` / `MemoryDelete` の自発的利用を安易に促さない。
|
||||||
|
- 通常作業では read/query を促し、write/edit/delete は明示的な依頼または memory maintenance worker に寄せる。
|
||||||
|
|
||||||
|
## 完了条件
|
||||||
|
|
||||||
|
- `resources/prompts/default.md` から memory guidance が render される。
|
||||||
|
- prompt render / catalog 関連 test があれば更新されている。
|
||||||
|
- internal worker prompt には不要な memory guidance が混ざらない。
|
||||||
|
- `cargo fmt --check` と関連 test が通る。
|
||||||
|
|
||||||
|
## 範囲外
|
||||||
|
|
||||||
|
- `summary.md` resident injection の実装。これは `memory-summary-resident-injection.md` で扱う。
|
||||||
|
- memory tool descriptions の大幅変更。
|
||||||
|
- memory usage metrics の設計変更。
|
||||||
|
- global memory / project local memory の store 分離。
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
<!-- event: migration author: tickets.sh-migration at: 2026-05-27T00:00:05Z -->
|
||||||
|
|
||||||
|
## Migrated
|
||||||
|
|
||||||
|
Migrated from tickets/memory-tool-guidance-prompt.md. No legacy review file was present at migration time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: close author: hare at: 2026-05-28T23:59:06Z status: closed -->
|
||||||
|
|
||||||
|
## Closed
|
||||||
|
|
||||||
|
---
|
||||||
|
id: 20260527-000005-memory-tool-guidance-prompt
|
||||||
|
slug: memory-tool-guidance-prompt
|
||||||
|
title: プロンプト: memory / knowledge tool 利用タイミングのガイダンス
|
||||||
|
status: closed
|
||||||
|
kind: task
|
||||||
|
priority: P2
|
||||||
|
labels: [migrated]
|
||||||
|
created_at: 2026-05-27T00:00:05Z
|
||||||
|
updated_at: 2026-05-28T23:59:06Z
|
||||||
|
assignee: null
|
||||||
|
legacy_ticket: tickets/memory-tool-guidance-prompt.md
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration reference
|
||||||
|
|
||||||
|
- legacy_ticket: tickets/memory-tool-guidance-prompt.md
|
||||||
|
- migrated_from: TODO.md / tickets directory migration on 2026-05-27
|
||||||
|
|
||||||
|
# プロンプト: memory / knowledge tool 利用タイミングのガイダンス
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
通常 Pod には `MemoryQuery` / `MemoryRead` / `KnowledgeQuery` / `MemoryWrite` 等の memory / knowledge tools が提供されているが、現状の通常 system prompt はそれらを「いつ使うべきか」をほとんど説明していない。
|
||||||
|
|
||||||
|
現在の `resources/prompts/common/tool-usage.md` は、既知パスなら Read、検索なら Grep/Glob、並列可能ならまとめる、という汎用 tool 方針に留まる。memory / knowledge tools の description には操作方法はあるが、モデルが自発的に memory lookup すべき状況は明示されていない。
|
||||||
|
|
||||||
|
このため、過去の決定・ユーザー嗜好・以前の経緯を問われても、モデルが `MemoryQuery` / `MemoryRead` を自発的に使わない可能性が高い。`summary.md` resident injection により短い durable context は常時見えるようになるが、詳細な過去判断や request を探すには query guidance が必要である。
|
||||||
|
|
||||||
|
## 方針
|
||||||
|
|
||||||
|
通常 Pod の system prompt に、memory / knowledge tools の利用タイミングを短く追加する。
|
||||||
|
|
||||||
|
目的は「必要な時に過去情報を探す」ことであり、毎 turn memory query を強制することではない。memory / knowledge は helpful context だが stale になり得るため、現在の user instruction / files / tickets / git state / session log を上書きする権威として扱わせない。
|
||||||
|
|
||||||
|
## 推奨する追加文言
|
||||||
|
|
||||||
|
`resources/prompts/common/tool-usage.md` に新しい小節を足すか、`resources/prompts/common/memory.md` を作って `default.md` から include する。
|
||||||
|
|
||||||
|
例:
|
||||||
|
|
||||||
|
```md
|
||||||
|
## Memory and knowledge
|
||||||
|
|
||||||
|
Use memory and knowledge tools when the user asks about past decisions, prior requests, durable preferences, project history, or why something was done. Do not guess from vague recollection when a targeted memory lookup would answer the question.
|
||||||
|
|
||||||
|
- Use `MemoryQuery` for durable memory records: summary, decisions, and requests.
|
||||||
|
- Use `KnowledgeQuery` for project knowledge records.
|
||||||
|
- Use `MemoryRead(kind=summary)` when you need the full workspace memory summary.
|
||||||
|
- Use `MemoryRead` on returned slugs when query excerpts are insufficient.
|
||||||
|
|
||||||
|
Resident memory and knowledge are helpful context but may be stale. Current user instructions, repository files, tickets, git history, and session logs are more authoritative for exact current state.
|
||||||
|
|
||||||
|
Do not query memory on every turn. Prefer it when past context, user preferences, or prior rationale materially affects the answer or implementation.
|
||||||
|
```
|
||||||
|
|
||||||
|
文言は実装時に自然に調整してよいが、以下の意味は維持する。
|
||||||
|
|
||||||
|
- 過去判断 / 過去依頼 / ユーザー嗜好 / project history / why 系では memory lookup を促す。
|
||||||
|
- `MemoryQuery`, `KnowledgeQuery`, `MemoryRead(kind=summary)`, slug read の役割を明示する。
|
||||||
|
- resident context は stale になり得ると明示する。
|
||||||
|
- current user instruction / files / tickets / git / session logs の方が exact current state では強いと明示する。
|
||||||
|
- 毎 turn query しないと明示する。
|
||||||
|
|
||||||
|
## 要件
|
||||||
|
|
||||||
|
- 通常 Pod の default prompt に memory / knowledge tool 利用タイミングの guidance が入る。
|
||||||
|
- internal prompts (`memory_extract_system`, `memory_consolidation_system`, `compact_system`) の挙動を変えない。
|
||||||
|
- guidance は短く、通常 turn の token overhead を過度に増やさない。
|
||||||
|
- guidance は memory / knowledge を current authority より上に置かない。
|
||||||
|
- guidance は毎 turn memory query を促さない。
|
||||||
|
- `MemoryWrite` / `MemoryEdit` / `MemoryDelete` の自発的利用を安易に促さない。
|
||||||
|
- 通常作業では read/query を促し、write/edit/delete は明示的な依頼または memory maintenance worker に寄せる。
|
||||||
|
|
||||||
|
## 完了条件
|
||||||
|
|
||||||
|
- `resources/prompts/default.md` から memory guidance が render される。
|
||||||
|
- prompt render / catalog 関連 test があれば更新されている。
|
||||||
|
- internal worker prompt には不要な memory guidance が混ざらない。
|
||||||
|
- `cargo fmt --check` と関連 test が通る。
|
||||||
|
|
||||||
|
## 範囲外
|
||||||
|
|
||||||
|
- `summary.md` resident injection の実装。これは `memory-summary-resident-injection.md` で扱う。
|
||||||
|
- memory tool descriptions の大幅変更。
|
||||||
|
- memory usage metrics の設計変更。
|
||||||
|
- global memory / project local memory の store 分離。
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
---
|
||||||
|
id: 20260528-233524-multi-pod-open-return
|
||||||
|
slug: multi-pod-open-return
|
||||||
|
title: Return to multi-Pod view after opening a Pod
|
||||||
|
status: closed
|
||||||
|
kind: task
|
||||||
|
priority: P2
|
||||||
|
labels: [tui, pod, ux]
|
||||||
|
created_at: 2026-05-28T23:35:24Z
|
||||||
|
updated_at: 2026-05-28T23:57:49Z
|
||||||
|
assignee: null
|
||||||
|
legacy_ticket: null
|
||||||
|
---
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
`tui --multi` can open the selected Pod with `o`. The current implementation treats this as leaving the multi-Pod dashboard and tail-calling the normal single-Pod TUI. Once the single-Pod screen is detached with `Ctrl+D` / `Ctrl+C`, the process exits instead of returning to the multi-Pod view.
|
||||||
|
|
||||||
|
For now, no special "back mode" or dedicated back key is needed. The desired behavior is simpler: when the user opens a Pod from the multi-Pod dashboard, the normal single-Pod attach screen runs as a nested attach session. When that session exits normally by detach/quit (`Ctrl+D`, `Ctrl+C`, or equivalent normal exit), control returns to the multi-Pod dashboard.
|
||||||
|
|
||||||
|
This should be implemented by abstracting the TUI launch/control flow, not by adding protocol features or making the single-Pod App deeply aware of multi-Pod mode.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- `tui --multi` remains the entrypoint for the multi-Pod dashboard.
|
||||||
|
- In `tui --multi`, pressing `o` on a selected openable Pod opens the normal single-Pod conversation screen.
|
||||||
|
- When that single-Pod screen exits normally, TUI returns to the multi-Pod dashboard instead of exiting the process.
|
||||||
|
- `Ctrl+D` / `Ctrl+C` detach/quit from the opened single-Pod screen should return to multi view.
|
||||||
|
- Normal single-Pod launches such as `tui <pod>` / `tui --pod <name>` should continue to exit the process on `Ctrl+D` / `Ctrl+C`.
|
||||||
|
- Avoid introducing a dedicated back key or back mode for this ticket.
|
||||||
|
- The caller loop determines whether a normal single-Pod exit returns to multi view or exits the process.
|
||||||
|
- Preserve multi-Pod dashboard state where practical.
|
||||||
|
- The selected Pod name should remain selected after returning, if still visible.
|
||||||
|
- The multi-Pod composer contents should be preserved across open/return.
|
||||||
|
- The Pod list should refresh after returning so status changes are visible.
|
||||||
|
- Keep terminal handling clean.
|
||||||
|
- Do not unnecessarily leave/re-enter alternate screen between multi view and the nested single-Pod screen if the existing terminal can be reused safely.
|
||||||
|
- If reusing the same `Terminal`, ensure cursor/mouse/raw-mode cleanup remains correct on final exit and on errors.
|
||||||
|
- Error handling should be explicit.
|
||||||
|
- If opening the selected Pod fails before the single-Pod screen starts, show a multi-view notice and keep the dashboard active.
|
||||||
|
- If the single-Pod session exits with a fatal error, return that error or show a clear diagnostic according to the existing TUI error behavior; do not silently swallow fatal failures.
|
||||||
|
- Existing `tui --multi` direct send behavior, section layout, and separator fix must continue to work.
|
||||||
|
|
||||||
|
## Suggested implementation direction
|
||||||
|
|
||||||
|
- Split the current single-Pod attach runner into a reusable function that accepts an existing `Terminal` and returns when the attached screen exits.
|
||||||
|
- Change `run_multi()` from one-shot `multi_pod::run(...).await -> Open -> run_pod_name(...)` into a controller loop:
|
||||||
|
|
||||||
|
```text
|
||||||
|
loop:
|
||||||
|
run multi dashboard with previous app state / selected Pod
|
||||||
|
Quit => exit process
|
||||||
|
Open(pod) => run single-Pod attach screen using the same terminal
|
||||||
|
on normal exit, refresh dashboard and continue loop
|
||||||
|
```
|
||||||
|
|
||||||
|
- Avoid adding a new protocol method. This is local TUI orchestration.
|
||||||
|
- Avoid making `App` carry a generic `BackToMulti` mode unless it is strictly necessary; prefer caller-owned control flow.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- From `tui --multi`, pressing `o` opens the selected Pod's normal conversation screen.
|
||||||
|
- Pressing `Ctrl+D` / `Ctrl+C` in that opened screen returns to the multi-Pod dashboard.
|
||||||
|
- Starting a single-Pod TUI directly still exits on `Ctrl+D` / `Ctrl+C`.
|
||||||
|
- Returning to multi view preserves multi composer contents and selected Pod name when possible.
|
||||||
|
- Returning to multi view refreshes Pod status/list.
|
||||||
|
- Opening failure from multi view leaves the user in multi view with a visible notice.
|
||||||
|
- Existing multi-Pod tests still pass.
|
||||||
|
- Focused tests cover the controller/runner behavior where possible, especially distinguishing direct single-Pod launch from multi-owned nested launch.
|
||||||
|
- `cargo fmt --check`
|
||||||
|
- `cargo test -p tui multi --no-default-features` or equivalent focused tests.
|
||||||
|
- `cargo check -p tui -p client -p pod`
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- Dedicated back key.
|
||||||
|
- Per-Pod detail panes inside the multi-Pod dashboard.
|
||||||
|
- Protocol changes.
|
||||||
|
- Changing direct-send semantics.
|
||||||
|
- Changing Pod visibility/discovery rules.
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
---
|
||||||
|
id: 20260528-233524-multi-pod-open-return
|
||||||
|
slug: multi-pod-open-return
|
||||||
|
title: Return to multi-Pod view after opening a Pod
|
||||||
|
status: closed
|
||||||
|
kind: task
|
||||||
|
priority: P2
|
||||||
|
labels: [tui, pod, ux]
|
||||||
|
created_at: 2026-05-28T23:35:24Z
|
||||||
|
updated_at: 2026-05-28T23:57:49Z
|
||||||
|
assignee: null
|
||||||
|
legacy_ticket: null
|
||||||
|
---
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
`tui --multi` can open the selected Pod with `o`. The current implementation treats this as leaving the multi-Pod dashboard and tail-calling the normal single-Pod TUI. Once the single-Pod screen is detached with `Ctrl+D` / `Ctrl+C`, the process exits instead of returning to the multi-Pod view.
|
||||||
|
|
||||||
|
For now, no special "back mode" or dedicated back key is needed. The desired behavior is simpler: when the user opens a Pod from the multi-Pod dashboard, the normal single-Pod attach screen runs as a nested attach session. When that session exits normally by detach/quit (`Ctrl+D`, `Ctrl+C`, or equivalent normal exit), control returns to the multi-Pod dashboard.
|
||||||
|
|
||||||
|
This should be implemented by abstracting the TUI launch/control flow, not by adding protocol features or making the single-Pod App deeply aware of multi-Pod mode.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- `tui --multi` remains the entrypoint for the multi-Pod dashboard.
|
||||||
|
- In `tui --multi`, pressing `o` on a selected openable Pod opens the normal single-Pod conversation screen.
|
||||||
|
- When that single-Pod screen exits normally, TUI returns to the multi-Pod dashboard instead of exiting the process.
|
||||||
|
- `Ctrl+D` / `Ctrl+C` detach/quit from the opened single-Pod screen should return to multi view.
|
||||||
|
- Normal single-Pod launches such as `tui <pod>` / `tui --pod <name>` should continue to exit the process on `Ctrl+D` / `Ctrl+C`.
|
||||||
|
- Avoid introducing a dedicated back key or back mode for this ticket.
|
||||||
|
- The caller loop determines whether a normal single-Pod exit returns to multi view or exits the process.
|
||||||
|
- Preserve multi-Pod dashboard state where practical.
|
||||||
|
- The selected Pod name should remain selected after returning, if still visible.
|
||||||
|
- The multi-Pod composer contents should be preserved across open/return.
|
||||||
|
- The Pod list should refresh after returning so status changes are visible.
|
||||||
|
- Keep terminal handling clean.
|
||||||
|
- Do not unnecessarily leave/re-enter alternate screen between multi view and the nested single-Pod screen if the existing terminal can be reused safely.
|
||||||
|
- If reusing the same `Terminal`, ensure cursor/mouse/raw-mode cleanup remains correct on final exit and on errors.
|
||||||
|
- Error handling should be explicit.
|
||||||
|
- If opening the selected Pod fails before the single-Pod screen starts, show a multi-view notice and keep the dashboard active.
|
||||||
|
- If the single-Pod session exits with a fatal error, return that error or show a clear diagnostic according to the existing TUI error behavior; do not silently swallow fatal failures.
|
||||||
|
- Existing `tui --multi` direct send behavior, section layout, and separator fix must continue to work.
|
||||||
|
|
||||||
|
## Suggested implementation direction
|
||||||
|
|
||||||
|
- Split the current single-Pod attach runner into a reusable function that accepts an existing `Terminal` and returns when the attached screen exits.
|
||||||
|
- Change `run_multi()` from one-shot `multi_pod::run(...).await -> Open -> run_pod_name(...)` into a controller loop:
|
||||||
|
|
||||||
|
```text
|
||||||
|
loop:
|
||||||
|
run multi dashboard with previous app state / selected Pod
|
||||||
|
Quit => exit process
|
||||||
|
Open(pod) => run single-Pod attach screen using the same terminal
|
||||||
|
on normal exit, refresh dashboard and continue loop
|
||||||
|
```
|
||||||
|
|
||||||
|
- Avoid adding a new protocol method. This is local TUI orchestration.
|
||||||
|
- Avoid making `App` carry a generic `BackToMulti` mode unless it is strictly necessary; prefer caller-owned control flow.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- From `tui --multi`, pressing `o` opens the selected Pod's normal conversation screen.
|
||||||
|
- Pressing `Ctrl+D` / `Ctrl+C` in that opened screen returns to the multi-Pod dashboard.
|
||||||
|
- Starting a single-Pod TUI directly still exits on `Ctrl+D` / `Ctrl+C`.
|
||||||
|
- Returning to multi view preserves multi composer contents and selected Pod name when possible.
|
||||||
|
- Returning to multi view refreshes Pod status/list.
|
||||||
|
- Opening failure from multi view leaves the user in multi view with a visible notice.
|
||||||
|
- Existing multi-Pod tests still pass.
|
||||||
|
- Focused tests cover the controller/runner behavior where possible, especially distinguishing direct single-Pod launch from multi-owned nested launch.
|
||||||
|
- `cargo fmt --check`
|
||||||
|
- `cargo test -p tui multi --no-default-features` or equivalent focused tests.
|
||||||
|
- `cargo check -p tui -p client -p pod`
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- Dedicated back key.
|
||||||
|
- Per-Pod detail panes inside the multi-Pod dashboard.
|
||||||
|
- Protocol changes.
|
||||||
|
- Changing direct-send semantics.
|
||||||
|
- Changing Pod visibility/discovery rules.
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
<!-- event: create author: tickets.sh at: 2026-05-28T23:35:24Z -->
|
||||||
|
|
||||||
|
## Created
|
||||||
|
|
||||||
|
Created by tickets.sh create.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: close author: hare at: 2026-05-28T23:57:49Z status: closed -->
|
||||||
|
|
||||||
|
## Closed
|
||||||
|
|
||||||
|
---
|
||||||
|
id: 20260528-233524-multi-pod-open-return
|
||||||
|
slug: multi-pod-open-return
|
||||||
|
title: Return to multi-Pod view after opening a Pod
|
||||||
|
status: closed
|
||||||
|
kind: task
|
||||||
|
priority: P2
|
||||||
|
labels: [tui, pod, ux]
|
||||||
|
created_at: 2026-05-28T23:35:24Z
|
||||||
|
updated_at: 2026-05-28T23:57:49Z
|
||||||
|
assignee: null
|
||||||
|
legacy_ticket: null
|
||||||
|
---
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
`tui --multi` can open the selected Pod with `o`. The current implementation treats this as leaving the multi-Pod dashboard and tail-calling the normal single-Pod TUI. Once the single-Pod screen is detached with `Ctrl+D` / `Ctrl+C`, the process exits instead of returning to the multi-Pod view.
|
||||||
|
|
||||||
|
For now, no special "back mode" or dedicated back key is needed. The desired behavior is simpler: when the user opens a Pod from the multi-Pod dashboard, the normal single-Pod attach screen runs as a nested attach session. When that session exits normally by detach/quit (`Ctrl+D`, `Ctrl+C`, or equivalent normal exit), control returns to the multi-Pod dashboard.
|
||||||
|
|
||||||
|
This should be implemented by abstracting the TUI launch/control flow, not by adding protocol features or making the single-Pod App deeply aware of multi-Pod mode.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- `tui --multi` remains the entrypoint for the multi-Pod dashboard.
|
||||||
|
- In `tui --multi`, pressing `o` on a selected openable Pod opens the normal single-Pod conversation screen.
|
||||||
|
- When that single-Pod screen exits normally, TUI returns to the multi-Pod dashboard instead of exiting the process.
|
||||||
|
- `Ctrl+D` / `Ctrl+C` detach/quit from the opened single-Pod screen should return to multi view.
|
||||||
|
- Normal single-Pod launches such as `tui <pod>` / `tui --pod <name>` should continue to exit the process on `Ctrl+D` / `Ctrl+C`.
|
||||||
|
- Avoid introducing a dedicated back key or back mode for this ticket.
|
||||||
|
- The caller loop determines whether a normal single-Pod exit returns to multi view or exits the process.
|
||||||
|
- Preserve multi-Pod dashboard state where practical.
|
||||||
|
- The selected Pod name should remain selected after returning, if still visible.
|
||||||
|
- The multi-Pod composer contents should be preserved across open/return.
|
||||||
|
- The Pod list should refresh after returning so status changes are visible.
|
||||||
|
- Keep terminal handling clean.
|
||||||
|
- Do not unnecessarily leave/re-enter alternate screen between multi view and the nested single-Pod screen if the existing terminal can be reused safely.
|
||||||
|
- If reusing the same `Terminal`, ensure cursor/mouse/raw-mode cleanup remains correct on final exit and on errors.
|
||||||
|
- Error handling should be explicit.
|
||||||
|
- If opening the selected Pod fails before the single-Pod screen starts, show a multi-view notice and keep the dashboard active.
|
||||||
|
- If the single-Pod session exits with a fatal error, return that error or show a clear diagnostic according to the existing TUI error behavior; do not silently swallow fatal failures.
|
||||||
|
- Existing `tui --multi` direct send behavior, section layout, and separator fix must continue to work.
|
||||||
|
|
||||||
|
## Suggested implementation direction
|
||||||
|
|
||||||
|
- Split the current single-Pod attach runner into a reusable function that accepts an existing `Terminal` and returns when the attached screen exits.
|
||||||
|
- Change `run_multi()` from one-shot `multi_pod::run(...).await -> Open -> run_pod_name(...)` into a controller loop:
|
||||||
|
|
||||||
|
```text
|
||||||
|
loop:
|
||||||
|
run multi dashboard with previous app state / selected Pod
|
||||||
|
Quit => exit process
|
||||||
|
Open(pod) => run single-Pod attach screen using the same terminal
|
||||||
|
on normal exit, refresh dashboard and continue loop
|
||||||
|
```
|
||||||
|
|
||||||
|
- Avoid adding a new protocol method. This is local TUI orchestration.
|
||||||
|
- Avoid making `App` carry a generic `BackToMulti` mode unless it is strictly necessary; prefer caller-owned control flow.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- From `tui --multi`, pressing `o` opens the selected Pod's normal conversation screen.
|
||||||
|
- Pressing `Ctrl+D` / `Ctrl+C` in that opened screen returns to the multi-Pod dashboard.
|
||||||
|
- Starting a single-Pod TUI directly still exits on `Ctrl+D` / `Ctrl+C`.
|
||||||
|
- Returning to multi view preserves multi composer contents and selected Pod name when possible.
|
||||||
|
- Returning to multi view refreshes Pod status/list.
|
||||||
|
- Opening failure from multi view leaves the user in multi view with a visible notice.
|
||||||
|
- Existing multi-Pod tests still pass.
|
||||||
|
- Focused tests cover the controller/runner behavior where possible, especially distinguishing direct single-Pod launch from multi-owned nested launch.
|
||||||
|
- `cargo fmt --check`
|
||||||
|
- `cargo test -p tui multi --no-default-features` or equivalent focused tests.
|
||||||
|
- `cargo check -p tui -p client -p pod`
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- Dedicated back key.
|
||||||
|
- Per-Pod detail panes inside the multi-Pod dashboard.
|
||||||
|
- Protocol changes.
|
||||||
|
- Changing direct-send semantics.
|
||||||
|
- Changing Pod visibility/discovery rules.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
<!-- event: migration author: tickets.sh-migration at: 2026-05-27T00:00:05Z -->
|
|
||||||
|
|
||||||
## Migrated
|
|
||||||
|
|
||||||
Migrated from tickets/memory-tool-guidance-prompt.md. No legacy review file was present at migration time.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
---
|
||||||
|
id: 20260529-001326-rename-installed-binaries
|
||||||
|
slug: rename-installed-binaries
|
||||||
|
title: Rename installed binaries
|
||||||
|
status: open
|
||||||
|
kind: task
|
||||||
|
priority: P2
|
||||||
|
labels: [cli, packaging, tui, pod]
|
||||||
|
created_at: 2026-05-29T00:13:26Z
|
||||||
|
updated_at: 2026-05-29T00:13:26Z
|
||||||
|
assignee: null
|
||||||
|
legacy_ticket: null
|
||||||
|
---
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
The workspace crate names `tui` and `pod` are useful internally, but the installed command names are too generic for user environments. `tui` does not identify the application, and `pod` collides with common terminology and other tooling.
|
||||||
|
|
||||||
|
Use application-specific binary names for installed commands:
|
||||||
|
|
||||||
|
- `insomnia`: the main terminal UI / user entrypoint, currently built from the `tui` crate.
|
||||||
|
- `insomnia-pod`: the Pod CLI/runtime command, currently built from the `pod` crate.
|
||||||
|
|
||||||
|
This is a command name change, not a crate rename. Keep the Rust crate/package names `tui` and `pod` unless there is a separate design decision to rename crates.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Rename Cargo binary outputs:
|
||||||
|
- `crates/tui` binary name becomes `insomnia`.
|
||||||
|
- `crates/pod` binary name becomes `insomnia-pod`.
|
||||||
|
- Do not add legacy `tui` / `pod` installed aliases unless a concrete internal dependency requires it and is documented.
|
||||||
|
- Update Nix packaging to install and check the new binary names.
|
||||||
|
- `$out/bin/insomnia`
|
||||||
|
- `$out/bin/insomnia-pod`
|
||||||
|
- Update flake apps to use the new command names.
|
||||||
|
- default app should run `insomnia`.
|
||||||
|
- expose app entries for `insomnia` and `insomnia-pod`.
|
||||||
|
- Update docs that instruct users to run `tui` / `pod` as installed commands.
|
||||||
|
- Keep references to crate/package names where they are explicitly Cargo package names, e.g. `cargo check -p tui`.
|
||||||
|
- Prefer `cargo run -p tui -- ...` in development docs if referring to crate-based development invocation, but installed usage should use `insomnia`.
|
||||||
|
- Audit code/tests/scripts for assumptions that installed binary names are `tui` or `pod`.
|
||||||
|
- Internal runtime process spawning must still work.
|
||||||
|
- If code intentionally uses Cargo package names, leave them unchanged.
|
||||||
|
- Keep CLI semantics unchanged except for command names.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- `cargo build -p tui -p pod` produces runnable binaries named `insomnia` and `insomnia-pod`.
|
||||||
|
- `cargo run -p tui -- --help` and `cargo run -p pod -- --help` still work as development package invocations.
|
||||||
|
- Installed/Nix package smoke checks look for `insomnia` and `insomnia-pod`, not `tui` and `pod`.
|
||||||
|
- `flake.nix` app outputs use the new binary names.
|
||||||
|
- User-facing docs no longer tell users to run installed commands as `tui` / `pod`.
|
||||||
|
- No legacy aliases are installed unless explicitly justified.
|
||||||
|
- `cargo fmt --check`
|
||||||
|
- Focused cargo checks/tests for affected crates, at least `cargo check -p tui -p pod`.
|
||||||
|
- Nix validation that does not require network where possible, e.g. `nix flake check --no-build`.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- Renaming crates/packages from `tui` / `pod`.
|
||||||
|
- Changing CLI argument semantics.
|
||||||
|
- Changing Pod protocol or socket behavior.
|
||||||
|
- Publishing or Home Manager module changes.
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<!-- event: create author: tickets.sh at: 2026-05-29T00:13:26Z -->
|
||||||
|
|
||||||
|
## Created
|
||||||
|
|
||||||
|
Created by tickets.sh create.
|
||||||
|
|
||||||
|
---
|
||||||
Loading…
Reference in New Issue
Block a user