Compare commits
11 Commits
30e9e65854
...
32ac0eee35
| Author | SHA1 | Date | |
|---|---|---|---|
| 32ac0eee35 | |||
| 0764275b44 | |||
| 277f94885f | |||
| 84819e2bf7 | |||
| 31f7db302d | |||
| 1c61cce2e7 | |||
| 7c7e11d3aa | |||
| 7a717f2d25 | |||
| 1247cdabc1 | |||
| 6812167422 | |||
| ed3e97c22a |
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -3911,6 +3911,7 @@ dependencies = [
|
||||||
"crossterm 0.28.1",
|
"crossterm 0.28.1",
|
||||||
"llm-worker",
|
"llm-worker",
|
||||||
"manifest",
|
"manifest",
|
||||||
|
"memory",
|
||||||
"pod-registry",
|
"pod-registry",
|
||||||
"pod-store",
|
"pod-store",
|
||||||
"protocol",
|
"protocol",
|
||||||
|
|
|
||||||
|
|
@ -442,17 +442,16 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn context_window_is_clamped_by_catalog_backend_max() {
|
fn codex_gpt55_catalog_records_effective_context_window() {
|
||||||
let providers = load_builtin_providers().unwrap();
|
let providers = load_builtin_providers().unwrap();
|
||||||
let models = load_builtin_models().unwrap();
|
let models = load_builtin_models().unwrap();
|
||||||
let manifest = ModelManifest {
|
let manifest = ModelManifest {
|
||||||
ref_: Some("codex-oauth/gpt-5.5".into()),
|
ref_: Some("codex-oauth/gpt-5.5".into()),
|
||||||
context_window: Some(1_000_000),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let cfg = resolve_with_catalogs(&manifest, &providers, &models).unwrap();
|
let cfg = resolve_with_catalogs(&manifest, &providers, &models).unwrap();
|
||||||
assert_eq!(cfg.context_window, 272_000);
|
assert_eq!(cfg.context_window, 272_000);
|
||||||
assert_eq!(cfg.max_context_window, Some(272_000));
|
assert_eq!(cfg.max_context_window, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -473,7 +472,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn manifest_backend_max_overrides_catalog_backend_max() {
|
fn manifest_backend_max_clamps_ref_context_override() {
|
||||||
let providers = load_builtin_providers().unwrap();
|
let providers = load_builtin_providers().unwrap();
|
||||||
let models = load_builtin_models().unwrap();
|
let models = load_builtin_models().unwrap();
|
||||||
let manifest = ModelManifest {
|
let manifest = ModelManifest {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ unicode-width = "0.2.2"
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
manifest = { workspace = true }
|
manifest = { workspace = true }
|
||||||
|
memory = { workspace = true }
|
||||||
session-store = { workspace = true }
|
session-store = { workspace = true }
|
||||||
pod-store = { workspace = true }
|
pod-store = { workspace = true }
|
||||||
pod-registry = { workspace = true }
|
pod-registry = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ mod cache;
|
||||||
mod command;
|
mod command;
|
||||||
mod input;
|
mod input;
|
||||||
mod markdown;
|
mod markdown;
|
||||||
|
mod memory_lint;
|
||||||
mod multi_pod;
|
mod multi_pod;
|
||||||
mod picker;
|
mod picker;
|
||||||
mod pod_list;
|
mod pod_list;
|
||||||
|
|
@ -80,12 +81,15 @@ enum Mode {
|
||||||
/// separate from `-r`/`--resume`, which keeps its single-Pod picker
|
/// separate from `-r`/`--resume`, which keeps its single-Pod picker
|
||||||
/// meaning.
|
/// meaning.
|
||||||
Multi,
|
Multi,
|
||||||
|
/// `insomnia memory lint`: headless lint for workspace memory and knowledge files.
|
||||||
|
MemoryLint(memory_lint::LintCliOptions),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum ParseError {
|
enum ParseError {
|
||||||
Conflict(&'static str),
|
Conflict(&'static str),
|
||||||
InvalidSession(String),
|
InvalidSession(String),
|
||||||
|
MemoryLint(memory_lint::UsageError),
|
||||||
MissingValue(&'static str),
|
MissingValue(&'static str),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,6 +98,7 @@ impl std::fmt::Display for ParseError {
|
||||||
match self {
|
match self {
|
||||||
Self::Conflict(message) => write!(f, "{message}"),
|
Self::Conflict(message) => write!(f, "{message}"),
|
||||||
Self::InvalidSession(s) => write!(f, "invalid --session UUID: {s}"),
|
Self::InvalidSession(s) => write!(f, "invalid --session UUID: {s}"),
|
||||||
|
Self::MemoryLint(err) => write!(f, "{err}"),
|
||||||
Self::MissingValue(flag) => write!(f, "{flag} requires a value"),
|
Self::MissingValue(flag) => write!(f, "{flag} requires a value"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -109,6 +114,13 @@ where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
let args: Vec<String> = args.into_iter().map(Into::into).collect();
|
let args: Vec<String> = args.into_iter().map(Into::into).collect();
|
||||||
|
if args.first().map(String::as_str) == Some("memory")
|
||||||
|
&& args.get(1).map(String::as_str) == Some("lint")
|
||||||
|
{
|
||||||
|
let options = memory_lint::parse_lint_args(&args[2..]).map_err(ParseError::MemoryLint)?;
|
||||||
|
return Ok(Mode::MemoryLint(options));
|
||||||
|
}
|
||||||
|
|
||||||
let mut resume = false;
|
let mut resume = false;
|
||||||
let mut multi = false;
|
let mut multi = false;
|
||||||
let mut session: Option<SegmentId> = None;
|
let mut session: Option<SegmentId> = None;
|
||||||
|
|
@ -255,10 +267,24 @@ async fn main() -> ExitCode {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("insomnia: {e}");
|
eprintln!("insomnia: {e}");
|
||||||
return ExitCode::FAILURE;
|
return match e {
|
||||||
|
ParseError::MemoryLint(_) => ExitCode::from(2),
|
||||||
|
_ => ExitCode::FAILURE,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Mode::MemoryLint(ref options) = mode {
|
||||||
|
return match memory_lint::run(options) {
|
||||||
|
Ok(memory_lint::LintStatus::Clean) => ExitCode::SUCCESS,
|
||||||
|
Ok(memory_lint::LintStatus::Failed) => ExitCode::FAILURE,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("insomnia: {err}");
|
||||||
|
ExitCode::from(2)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = enable_raw_mode() {
|
if let Err(e) = enable_raw_mode() {
|
||||||
eprintln!("insomnia: failed to enter raw mode: {e}");
|
eprintln!("insomnia: failed to enter raw mode: {e}");
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
|
|
@ -278,6 +304,7 @@ async fn main() -> ExitCode {
|
||||||
Mode::Resume => run_resume().await,
|
Mode::Resume => run_resume().await,
|
||||||
Mode::ResumeWithSession(id) => run_spawn(Some(id), None).await,
|
Mode::ResumeWithSession(id) => run_spawn(Some(id), None).await,
|
||||||
Mode::Multi => run_multi().await,
|
Mode::Multi => run_multi().await,
|
||||||
|
Mode::MemoryLint(_) => unreachable!("memory lint returns before terminal setup"),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Always restore the terminal first so any pending eprintln below
|
// Always restore the terminal first so any pending eprintln below
|
||||||
|
|
@ -1169,6 +1196,67 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_memory_alone_remains_positional_pod_name() {
|
||||||
|
match parse_args_from(["memory"]).unwrap() {
|
||||||
|
Mode::PodName {
|
||||||
|
pod_name,
|
||||||
|
socket_override,
|
||||||
|
} => {
|
||||||
|
assert_eq!(pod_name, "memory");
|
||||||
|
assert_eq!(socket_override, None);
|
||||||
|
}
|
||||||
|
_ => panic!("expected PodName mode"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_memory_lint_mode() {
|
||||||
|
match parse_args_from([
|
||||||
|
"memory",
|
||||||
|
"lint",
|
||||||
|
"--workspace",
|
||||||
|
"/tmp/ws",
|
||||||
|
"--json",
|
||||||
|
"--warnings-as-errors",
|
||||||
|
])
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
Mode::MemoryLint(options) => {
|
||||||
|
assert_eq!(options.workspace, Some(PathBuf::from("/tmp/ws")));
|
||||||
|
assert!(options.json);
|
||||||
|
assert!(options.warnings_as_errors);
|
||||||
|
}
|
||||||
|
_ => panic!("expected MemoryLint mode"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_memory_lint_rejects_usage_errors() {
|
||||||
|
let err = parse_args_from(["memory", "lint", "--workspace"]).unwrap_err();
|
||||||
|
assert_eq!(err.to_string(), "--workspace requires a value");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_memory_lint_workspace_equals() {
|
||||||
|
match parse_args_from(["memory", "lint", "--workspace=/tmp/ws"]).unwrap() {
|
||||||
|
Mode::MemoryLint(options) => {
|
||||||
|
assert_eq!(options.workspace, Some(PathBuf::from("/tmp/ws")));
|
||||||
|
assert!(!options.json);
|
||||||
|
assert!(!options.warnings_as_errors);
|
||||||
|
}
|
||||||
|
_ => panic!("expected MemoryLint mode"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn memory_lint_with_other_second_word_remains_positional_pod_name() {
|
||||||
|
match parse_args_from(["memory", "other"]).unwrap() {
|
||||||
|
Mode::PodName { pod_name, .. } => assert_eq!(pod_name, "memory"),
|
||||||
|
_ => panic!("expected PodName mode"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_rejects_pod_and_session() {
|
fn parse_rejects_pod_and_session() {
|
||||||
let segment_id = session_store::new_segment_id().to_string();
|
let segment_id = session_store::new_segment_id().to_string();
|
||||||
|
|
|
||||||
515
crates/tui/src/memory_lint.rs
Normal file
515
crates/tui/src/memory_lint.rs
Normal file
|
|
@ -0,0 +1,515 @@
|
||||||
|
use std::fmt;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use memory::linter::WriteMode;
|
||||||
|
use memory::{Linter, WorkspaceLayout};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct LintCliOptions {
|
||||||
|
pub workspace: Option<PathBuf>,
|
||||||
|
pub json: bool,
|
||||||
|
pub warnings_as_errors: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LintCliOptions {
|
||||||
|
fn workspace_root(&self) -> Result<PathBuf, LintCliError> {
|
||||||
|
let cwd = std::env::current_dir().map_err(LintCliError::CurrentDir)?;
|
||||||
|
Ok(match &self.workspace {
|
||||||
|
Some(path) if path.is_absolute() => path.clone(),
|
||||||
|
Some(path) => cwd.join(path),
|
||||||
|
None => cwd,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum UsageError {
|
||||||
|
MissingValue(&'static str),
|
||||||
|
UnknownArgument(String),
|
||||||
|
UnexpectedArgument(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for UsageError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::MissingValue(flag) => write!(f, "{flag} requires a value"),
|
||||||
|
Self::UnknownArgument(arg) => write!(f, "unknown memory lint argument: {arg}"),
|
||||||
|
Self::UnexpectedArgument(arg) => write!(f, "unexpected memory lint argument: {arg}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_lint_args(args: &[String]) -> Result<LintCliOptions, UsageError> {
|
||||||
|
let mut options = LintCliOptions {
|
||||||
|
workspace: None,
|
||||||
|
json: false,
|
||||||
|
warnings_as_errors: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while i < args.len() {
|
||||||
|
match args[i].as_str() {
|
||||||
|
"--json" => {
|
||||||
|
options.json = true;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
"--warnings-as-errors" => {
|
||||||
|
options.warnings_as_errors = true;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
"--workspace" => {
|
||||||
|
let raw = args
|
||||||
|
.get(i + 1)
|
||||||
|
.ok_or(UsageError::MissingValue("--workspace"))?;
|
||||||
|
if raw.starts_with('-') {
|
||||||
|
return Err(UsageError::MissingValue("--workspace"));
|
||||||
|
}
|
||||||
|
options.workspace = Some(PathBuf::from(raw));
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
arg if arg.starts_with("--workspace=") => {
|
||||||
|
let value = arg.trim_start_matches("--workspace=");
|
||||||
|
if value.is_empty() {
|
||||||
|
return Err(UsageError::MissingValue("--workspace"));
|
||||||
|
}
|
||||||
|
options.workspace = Some(PathBuf::from(value));
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
arg if arg.starts_with('-') => {
|
||||||
|
return Err(UsageError::UnknownArgument(arg.to_string()));
|
||||||
|
}
|
||||||
|
arg => return Err(UsageError::UnexpectedArgument(arg.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum LintStatus {
|
||||||
|
Clean,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum LintCliError {
|
||||||
|
CurrentDir(io::Error),
|
||||||
|
Io { path: PathBuf, source: io::Error },
|
||||||
|
Output(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for LintCliError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::CurrentDir(source) => write!(f, "failed to resolve current directory: {source}"),
|
||||||
|
Self::Io { path, source } => write!(f, "io error at {}: {source}", path.display()),
|
||||||
|
Self::Output(source) => write!(f, "failed to write lint output: {source}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for LintCliError {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
match self {
|
||||||
|
Self::CurrentDir(source) | Self::Io { source, .. } | Self::Output(source) => {
|
||||||
|
Some(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(options: &LintCliOptions) -> Result<LintStatus, LintCliError> {
|
||||||
|
let stdout = io::stdout();
|
||||||
|
run_with_writer(options, stdout.lock())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_with_writer<W: Write>(
|
||||||
|
options: &LintCliOptions,
|
||||||
|
mut writer: W,
|
||||||
|
) -> Result<LintStatus, LintCliError> {
|
||||||
|
let workspace = options.workspace_root()?;
|
||||||
|
let report = lint_workspace(&workspace)?;
|
||||||
|
|
||||||
|
if options.json {
|
||||||
|
write_json_report(&mut writer, &report)?;
|
||||||
|
} else {
|
||||||
|
write_human_report(&mut writer, &report)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if report.counts.errors > 0 || (options.warnings_as_errors && report.counts.warnings > 0) {
|
||||||
|
Ok(LintStatus::Failed)
|
||||||
|
} else {
|
||||||
|
Ok(LintStatus::Clean)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct WorkspaceLintReport {
|
||||||
|
pub workspace: String,
|
||||||
|
pub files: Vec<FileLintReport>,
|
||||||
|
pub errors: Vec<LintDiagnostic>,
|
||||||
|
pub warnings: Vec<LintDiagnostic>,
|
||||||
|
pub counts: LintCounts,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct FileLintReport {
|
||||||
|
pub path: String,
|
||||||
|
pub errors: Vec<String>,
|
||||||
|
pub warnings: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct LintDiagnostic {
|
||||||
|
pub path: String,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default, Serialize)]
|
||||||
|
pub struct LintCounts {
|
||||||
|
pub files: usize,
|
||||||
|
pub errors: usize,
|
||||||
|
pub warnings: usize,
|
||||||
|
pub failing_files: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lint_workspace(workspace: &Path) -> Result<WorkspaceLintReport, LintCliError> {
|
||||||
|
let layout = WorkspaceLayout::new(workspace.to_path_buf());
|
||||||
|
let linter = Linter::new(layout.clone());
|
||||||
|
let mut paths = collect_record_paths(&layout)?;
|
||||||
|
paths.sort();
|
||||||
|
|
||||||
|
let mut files = Vec::with_capacity(paths.len());
|
||||||
|
for path in paths {
|
||||||
|
let content = std::fs::read_to_string(&path).map_err(|source| LintCliError::Io {
|
||||||
|
path: path.clone(),
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
let lint = linter.lint(&path, &content, WriteMode::Update);
|
||||||
|
files.push(FileLintReport {
|
||||||
|
path: display_path(workspace, &path),
|
||||||
|
errors: lint.errors.iter().map(ToString::to_string).collect(),
|
||||||
|
warnings: lint.warnings.iter().map(ToString::to_string).collect(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let counts = count_files(&files);
|
||||||
|
let errors = diagnostics_for(&files, DiagnosticKind::Error);
|
||||||
|
let warnings = diagnostics_for(&files, DiagnosticKind::Warning);
|
||||||
|
Ok(WorkspaceLintReport {
|
||||||
|
workspace: workspace.display().to_string(),
|
||||||
|
files,
|
||||||
|
errors,
|
||||||
|
warnings,
|
||||||
|
counts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_record_paths(layout: &WorkspaceLayout) -> Result<Vec<PathBuf>, LintCliError> {
|
||||||
|
let mut paths = Vec::new();
|
||||||
|
|
||||||
|
let summary = layout.summary_path();
|
||||||
|
if is_file(&summary)? {
|
||||||
|
paths.push(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_md_files(&layout.decisions_dir(), &mut paths)?;
|
||||||
|
collect_md_files(&layout.requests_dir(), &mut paths)?;
|
||||||
|
collect_md_files(&layout.knowledge_dir(), &mut paths)?;
|
||||||
|
|
||||||
|
Ok(paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_file(path: &Path) -> Result<bool, LintCliError> {
|
||||||
|
match std::fs::metadata(path) {
|
||||||
|
Ok(meta) => Ok(meta.is_file()),
|
||||||
|
Err(source) if source.kind() == io::ErrorKind::NotFound => Ok(false),
|
||||||
|
Err(source) => Err(LintCliError::Io {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
source,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_md_files(dir: &Path, paths: &mut Vec<PathBuf>) -> Result<(), LintCliError> {
|
||||||
|
let entries = match std::fs::read_dir(dir) {
|
||||||
|
Ok(entries) => entries,
|
||||||
|
Err(source) if source.kind() == io::ErrorKind::NotFound => return Ok(()),
|
||||||
|
Err(source) => {
|
||||||
|
return Err(LintCliError::Io {
|
||||||
|
path: dir.to_path_buf(),
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
let entry = entry.map_err(|source| LintCliError::Io {
|
||||||
|
path: dir.to_path_buf(),
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
let path = entry.path();
|
||||||
|
let file_type = entry.file_type().map_err(|source| LintCliError::Io {
|
||||||
|
path: path.clone(),
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
if file_type.is_file() && path.extension().and_then(|ext| ext.to_str()) == Some("md") {
|
||||||
|
paths.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diagnostics_for(files: &[FileLintReport], kind: DiagnosticKind) -> Vec<LintDiagnostic> {
|
||||||
|
files
|
||||||
|
.iter()
|
||||||
|
.flat_map(|file| {
|
||||||
|
let messages = match kind {
|
||||||
|
DiagnosticKind::Error => &file.errors,
|
||||||
|
DiagnosticKind::Warning => &file.warnings,
|
||||||
|
};
|
||||||
|
messages.iter().map(|message| LintDiagnostic {
|
||||||
|
path: file.path.clone(),
|
||||||
|
message: message.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum DiagnosticKind {
|
||||||
|
Error,
|
||||||
|
Warning,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_files(files: &[FileLintReport]) -> LintCounts {
|
||||||
|
files.iter().fold(
|
||||||
|
LintCounts {
|
||||||
|
files: files.len(),
|
||||||
|
..LintCounts::default()
|
||||||
|
},
|
||||||
|
|mut counts, file| {
|
||||||
|
counts.errors += file.errors.len();
|
||||||
|
counts.warnings += file.warnings.len();
|
||||||
|
if !file.errors.is_empty() {
|
||||||
|
counts.failing_files += 1;
|
||||||
|
}
|
||||||
|
counts
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_path(workspace: &Path, path: &Path) -> String {
|
||||||
|
path.strip_prefix(workspace)
|
||||||
|
.unwrap_or(path)
|
||||||
|
.display()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_human_report<W: Write>(
|
||||||
|
writer: &mut W,
|
||||||
|
report: &WorkspaceLintReport,
|
||||||
|
) -> Result<(), LintCliError> {
|
||||||
|
writeln!(writer, "Workspace: {}", report.workspace).map_err(LintCliError::Output)?;
|
||||||
|
for file in &report.files {
|
||||||
|
let status = if file.errors.is_empty() && file.warnings.is_empty() {
|
||||||
|
"ok"
|
||||||
|
} else if file.errors.is_empty() {
|
||||||
|
"warning"
|
||||||
|
} else {
|
||||||
|
"error"
|
||||||
|
};
|
||||||
|
writeln!(writer, "{status}: {}", file.path).map_err(LintCliError::Output)?;
|
||||||
|
for error in &file.errors {
|
||||||
|
writeln!(writer, " error: {error}").map_err(LintCliError::Output)?;
|
||||||
|
}
|
||||||
|
for warning in &file.warnings {
|
||||||
|
writeln!(writer, " warning: {warning}").map_err(LintCliError::Output)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"Summary: files={} errors={} warnings={} failing_files={}",
|
||||||
|
report.counts.files,
|
||||||
|
report.counts.errors,
|
||||||
|
report.counts.warnings,
|
||||||
|
report.counts.failing_files
|
||||||
|
)
|
||||||
|
.map_err(LintCliError::Output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_json_report<W: Write>(
|
||||||
|
writer: &mut W,
|
||||||
|
report: &WorkspaceLintReport,
|
||||||
|
) -> Result<(), LintCliError> {
|
||||||
|
serde_json::to_writer_pretty(&mut *writer, report)
|
||||||
|
.map_err(|source| LintCliError::Output(io::Error::other(source)))?;
|
||||||
|
writeln!(writer).map_err(LintCliError::Output)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use serde_json::Value;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
fn write(path: &Path, content: &str) {
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).unwrap();
|
||||||
|
}
|
||||||
|
std::fs::write(path, content).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_summary() -> &'static str {
|
||||||
|
"---\nupdated_at: 2026-05-31T00:00:00Z\n---\nsummary body\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_request() -> &'static str {
|
||||||
|
"---\ncreated_at: 2026-05-31T00:00:00Z\nupdated_at: 2026-05-31T00:00:00Z\nsources: []\n---\nrequest body\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn warning_request() -> String {
|
||||||
|
format!(
|
||||||
|
"---\ncreated_at: 2026-05-31T00:00:00Z\nupdated_at: 2026-05-31T00:00:00Z\nsources:\n - segment_id: seg\n range: [0, 1]\n---\n{}\n",
|
||||||
|
"x".repeat(1500)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_lint_options() {
|
||||||
|
let args = vec![
|
||||||
|
"--workspace".to_string(),
|
||||||
|
"/tmp/ws".to_string(),
|
||||||
|
"--json".to_string(),
|
||||||
|
"--warnings-as-errors".to_string(),
|
||||||
|
];
|
||||||
|
let parsed = parse_lint_args(&args).unwrap();
|
||||||
|
assert_eq!(parsed.workspace, Some(PathBuf::from("/tmp/ws")));
|
||||||
|
assert!(parsed.json);
|
||||||
|
assert!(parsed.warnings_as_errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_lint_usage_errors() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_lint_args(&["--workspace".to_string()]).unwrap_err(),
|
||||||
|
UsageError::MissingValue("--workspace")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_lint_args(&["--workspace".to_string(), "--json".to_string()]).unwrap_err(),
|
||||||
|
UsageError::MissingValue("--workspace")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_lint_args(&["--bogus".to_string()]).unwrap_err(),
|
||||||
|
UsageError::UnknownArgument("--bogus".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_lint_args(&["extra".to_string()]).unwrap_err(),
|
||||||
|
UsageError::UnexpectedArgument("extra".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lints_only_workspace_memory_and_knowledge_records() {
|
||||||
|
let dir = TempDir::new().unwrap();
|
||||||
|
let root = dir.path();
|
||||||
|
write(&root.join(".insomnia/memory/summary.md"), valid_summary());
|
||||||
|
write(
|
||||||
|
&root.join(".insomnia/memory/requests/request-one.md"),
|
||||||
|
valid_request(),
|
||||||
|
);
|
||||||
|
write(
|
||||||
|
&root.join(".insomnia/memory/_logs/ignored.md"),
|
||||||
|
"not frontmatter",
|
||||||
|
);
|
||||||
|
write(
|
||||||
|
&root.join(".insomnia/workflow/ignored.md"),
|
||||||
|
"not frontmatter",
|
||||||
|
);
|
||||||
|
|
||||||
|
let report = lint_workspace(root).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
report
|
||||||
|
.files
|
||||||
|
.iter()
|
||||||
|
.map(|file| file.path.as_str())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
vec![
|
||||||
|
".insomnia/memory/requests/request-one.md",
|
||||||
|
".insomnia/memory/summary.md",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(report.counts.files, 2);
|
||||||
|
assert_eq!(report.counts.errors, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_records_count_as_lint_failures() {
|
||||||
|
let dir = TempDir::new().unwrap();
|
||||||
|
let root = dir.path();
|
||||||
|
write(
|
||||||
|
&root.join(".insomnia/memory/summary.md"),
|
||||||
|
"missing frontmatter",
|
||||||
|
);
|
||||||
|
|
||||||
|
let report = lint_workspace(root).unwrap();
|
||||||
|
assert_eq!(report.counts.files, 1);
|
||||||
|
assert_eq!(report.counts.errors, 1);
|
||||||
|
assert_eq!(report.counts.failing_files, 1);
|
||||||
|
assert!(report.files[0].errors[0].contains("frontmatter"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn warnings_as_errors_changes_status_without_changing_report() {
|
||||||
|
let dir = TempDir::new().unwrap();
|
||||||
|
let root = dir.path();
|
||||||
|
write(
|
||||||
|
&root.join(".insomnia/memory/requests/large-record.md"),
|
||||||
|
&warning_request(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut output = Vec::new();
|
||||||
|
let status = run_with_writer(
|
||||||
|
&LintCliOptions {
|
||||||
|
workspace: Some(root.to_path_buf()),
|
||||||
|
json: false,
|
||||||
|
warnings_as_errors: true,
|
||||||
|
},
|
||||||
|
&mut output,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(status, LintStatus::Failed);
|
||||||
|
let text = String::from_utf8(output).unwrap();
|
||||||
|
assert!(text.contains("warnings=1"));
|
||||||
|
assert!(text.contains("errors=0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json_output_is_machine_readable() {
|
||||||
|
let dir = TempDir::new().unwrap();
|
||||||
|
let root = dir.path();
|
||||||
|
write(&root.join(".insomnia/memory/summary.md"), valid_summary());
|
||||||
|
|
||||||
|
let mut output = Vec::new();
|
||||||
|
let status = run_with_writer(
|
||||||
|
&LintCliOptions {
|
||||||
|
workspace: Some(root.to_path_buf()),
|
||||||
|
json: true,
|
||||||
|
warnings_as_errors: false,
|
||||||
|
},
|
||||||
|
&mut output,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(status, LintStatus::Clean);
|
||||||
|
let parsed: Value = serde_json::from_slice(&output).unwrap();
|
||||||
|
assert_eq!(parsed["workspace"], root.display().to_string());
|
||||||
|
assert_eq!(parsed["counts"]["files"], 1);
|
||||||
|
assert_eq!(parsed["files"][0]["path"], ".insomnia/memory/summary.md");
|
||||||
|
assert!(parsed["files"][0]["errors"].as_array().unwrap().is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
65
docs/report/2026-05-31-child-pod-visibility-restore-loss.md
Normal file
65
docs/report/2026-05-31-child-pod-visibility-restore-loss.md
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Child Pod visibility/restore loss during review flow
|
||||||
|
|
||||||
|
Date: 2026-05-31
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
During the `workspace-memory-lint-cli` review flow, a spawned reviewer Pod appeared to stop producing notifications/output and then became impossible to attach/restore from the parent Pod. The parent later saw no spawned Pods at all, while a restore/prune notification reported that missing or unreachable delegated child Pods had been reclaimed.
|
||||||
|
|
||||||
|
This looks like a control-plane visibility/restore issue rather than an implementation-review issue. The lost Pod was read-only and the review was safely re-run in a new reviewer Pod, but the incident is worth recording because it undermines long-running multi-agent workflows.
|
||||||
|
|
||||||
|
## Observed sequence
|
||||||
|
|
||||||
|
1. `workspace-memory-lint-coder-20260531` completed implementation and reported commit `7a717f2 cli: add workspace memory lint`.
|
||||||
|
2. A read-only reviewer Pod was spawned:
|
||||||
|
- `workspace-memory-lint-reviewer-20260531`
|
||||||
|
- read scope: main workspace and `.worktree/workspace-memory-lint-cli`
|
||||||
|
3. Repeated `ReadPodOutput` calls returned:
|
||||||
|
- `running; no new assistant text`
|
||||||
|
4. `InspectPod` still saw the reviewer as live/reachable/running at one point:
|
||||||
|
- socket: `/run/user/1000/insomnia/workspace-memory-lint-reviewer-20260531/sock`
|
||||||
|
- restore impossible only because the segment was locked by that live Pod
|
||||||
|
5. Later, after the user asked to restore it, `AttachOrRestorePod` failed:
|
||||||
|
- `pod workspace-memory-lint-reviewer-20260531 is not visible to this Pod`
|
||||||
|
6. `ListPods` then reported no spawned Pods, and `ListVisiblePods` only showed the self Pod `insomnia`.
|
||||||
|
7. A notification appeared:
|
||||||
|
- `Restored Pod state contained missing or unreachable delegated child Pods; their delegated write scopes were reclaimed before resume.`
|
||||||
|
8. The review had to be re-run by spawning a new read-only reviewer:
|
||||||
|
- `workspace-memory-lint-reviewer-rerun-20260531`
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Parent-side orchestration lost track of a child reviewer Pod that had previously been visible.
|
||||||
|
- The parent could not attach/restore by name because the child was no longer visible to the parent Pod.
|
||||||
|
- Any review result already produced by the lost child would have been hard to recover through normal parent tools.
|
||||||
|
- Multi-agent workflows that rely on long-running reviewer/coder Pods become less reliable if spawned-child visibility can disappear during parent resume/restore/prune.
|
||||||
|
|
||||||
|
In this instance the practical impact was low because the reviewer had read-only scope and the review could be re-run. The incident would be more serious for implementation Pods with unmerged write-scope work or for expensive/long review tasks.
|
||||||
|
|
||||||
|
## Why this matters
|
||||||
|
|
||||||
|
The current design intent is that Pod metadata is durable current state and spawned child registry persistence reuses Pod metadata. Parent-side tools should be able to inspect/attach/restore visible spawned children where durable state still records them, and pruning should be conservative enough not to erase reachable or recoverable child work prematurely.
|
||||||
|
|
||||||
|
This incident suggests at least one of these paths needs inspection:
|
||||||
|
|
||||||
|
- parent spawned-child registry persistence/restoration;
|
||||||
|
- pruning of unreachable children during parent restore;
|
||||||
|
- visibility rules for previously spawned child Pods after parent resume;
|
||||||
|
- distinction between live socket reachability, durable pod-store metadata, and parent-visible child registry;
|
||||||
|
- notification/read-output cursor behavior when a child is still running but no output arrives.
|
||||||
|
|
||||||
|
## Notes for follow-up
|
||||||
|
|
||||||
|
- The failure mode was not simply “child stopped”; the parent tool reported “not visible to this Pod,” which is different from stopped/unreachable.
|
||||||
|
- `InspectPod` had previously seen the child as live and locked; later `ListPods` returned no spawned Pods.
|
||||||
|
- The prune/reclaim notification may have happened after parent restore and may have removed child visibility state.
|
||||||
|
- A useful regression test would simulate parent restore with a child that is pending/running/unreachable at different phases and assert whether it remains visible, attachable, or intentionally pruned with a recoverable diagnostic.
|
||||||
|
- A workflow-level mitigation is to write important reviewer/coder outputs into ticket threads/artifacts promptly after reading them, and to re-run read-only reviewers if child visibility is lost.
|
||||||
|
|
||||||
|
## Current workaround
|
||||||
|
|
||||||
|
For `workspace-memory-lint-cli`, a replacement reviewer Pod was spawned with the same read-only task:
|
||||||
|
|
||||||
|
- `workspace-memory-lint-reviewer-rerun-20260531`
|
||||||
|
|
||||||
|
The original reviewer Pod was treated as lost/unrecoverable from the parent after `AttachOrRestorePod` reported it was not visible.
|
||||||
|
|
@ -32,8 +32,10 @@ context_window = 256000
|
||||||
[[model]]
|
[[model]]
|
||||||
id = "gpt-5.5"
|
id = "gpt-5.5"
|
||||||
provider = "codex-oauth"
|
provider = "codex-oauth"
|
||||||
context_window = 1050000
|
# OpenAI docs advertise GPT-5.5 with a 1.05M context window, but Codex OAuth /
|
||||||
max_context_window = 272000
|
# ChatGPT backend access is effectively limited around 272k tokens; this
|
||||||
|
# provider-specific entry records that effective route limit directly.
|
||||||
|
context_window = 272000
|
||||||
capability = { tool_calling = "parallel", structured_output = "json_schema", reasoning = "effort", vision = true, prompt_caching = { kind = "auto" } }
|
capability = { tool_calling = "parallel", structured_output = "json_schema", reasoning = "effort", vision = true, prompt_caching = { kind = "auto" } }
|
||||||
|
|
||||||
[[model]]
|
[[model]]
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
id: 20260527-000019-workspace-memory-lint-cli
|
id: 20260527-000019-workspace-memory-lint-cli
|
||||||
slug: workspace-memory-lint-cli
|
slug: workspace-memory-lint-cli
|
||||||
title: ワークスペースのメモリーをLintするヘッドレスCLI
|
title: ワークスペースのメモリーをLintするヘッドレスCLI
|
||||||
status: open
|
status: closed
|
||||||
kind: task
|
kind: task
|
||||||
priority: P2
|
priority: P2
|
||||||
labels: [migrated, memory, cli]
|
labels: [migrated, memory, cli]
|
||||||
created_at: 2026-05-27T00:00:19Z
|
created_at: 2026-05-27T00:00:19Z
|
||||||
updated_at: 2026-05-31T00:51:55Z
|
updated_at: 2026-05-31T02:15:17Z
|
||||||
assignee: null
|
assignee: null
|
||||||
legacy_ticket: null
|
legacy_ticket: null
|
||||||
---
|
---
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Implemented `insomnia memory lint` as a headless command in the existing user-facing `insomnia` binary. The command lints workspace memory/knowledge records with the existing `memory::Linter` using `WriteMode::Update`, supports human and JSON output, handles warnings-as-errors, preserves `insomnia memory` as a positional Pod name, and returns before TUI/raw-terminal or Pod connection paths. External review approved and validation passed.
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
<!-- event: migration author: tickets.sh-migration at: 2026-05-27T00:00:19Z -->
|
||||||
|
|
||||||
|
## Migrated
|
||||||
|
|
||||||
|
Migrated from TODO.md entry without a legacy ticket file. No legacy review file was present at migration time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: plan author: hare at: 2026-05-31T00:51:55Z -->
|
||||||
|
|
||||||
|
## Plan
|
||||||
|
|
||||||
|
Planning note:
|
||||||
|
|
||||||
|
- Keep this in the existing user-facing `insomnia` binary implemented by the `tui` crate. Do not add another installed command for this ticket.
|
||||||
|
- The command should be headless: parse args, lint files, print report, exit. It must not initialize terminal UI or connect to a Pod.
|
||||||
|
- `insomnia memory lint` is preferred, but `insomnia memory` alone should continue to be a valid Pod-name attach/create path if practical with the current parser.
|
||||||
|
- Use `memory::Linter` directly so CLI behavior tracks tool pre-write validation. Existing files should be linted with `WriteMode::Update`.
|
||||||
|
- Keep crate rename / single-binary architecture as future cleanup, not part of this ticket.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: hare at: 2026-05-31T02:14:28Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Implementation report from coder Pod `workspace-memory-lint-coder-20260531`:
|
||||||
|
|
||||||
|
- Branch: `workspace-memory-lint-cli`
|
||||||
|
- Commit: `7a717f2d259563df562913e0c3ceb388b094b697` (`cli: add workspace memory lint`)
|
||||||
|
- Added `insomnia memory lint [--workspace <PATH>] [--json] [--warnings-as-errors]` as a headless mode in the existing `tui` crate/user-facing `insomnia` binary.
|
||||||
|
- `insomnia memory` alone remains a positional Pod name.
|
||||||
|
- The lint command resolves workspace root, collects existing summary/decisions/requests/knowledge records through `memory::WorkspaceLayout`, and lints with existing `memory::Linter` using `WriteMode::Update`.
|
||||||
|
- The command prints deterministic human output by default and stable JSON with workspace/files/errors/warnings/counts when `--json` is requested.
|
||||||
|
- Exit codes follow the ticket: 0 clean, 1 lint failures or warnings-as-errors, 2 usage/I/O/output/runtime failures.
|
||||||
|
- The headless path returns before raw terminal setup or Pod connection/spawn logic.
|
||||||
|
|
||||||
|
Validation reported by coder:
|
||||||
|
|
||||||
|
- `cargo fmt --check` passed
|
||||||
|
- `cargo test -p tui memory_lint -- --nocapture` passed
|
||||||
|
- `cargo test -p tui` passed
|
||||||
|
- `cargo check -p tui` passed
|
||||||
|
- `./tickets.sh doctor` passed
|
||||||
|
- `git diff --check` passed
|
||||||
|
|
||||||
|
Unresolved issues: none.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: review author: hare at: 2026-05-31T02:14:28Z status: approve -->
|
||||||
|
|
||||||
|
## Review: approve
|
||||||
|
|
||||||
|
External review by reviewer Pod `workspace-memory-lint-reviewer-rerun-20260531`: approve.
|
||||||
|
|
||||||
|
The original reviewer Pod `workspace-memory-lint-reviewer-20260531` became non-visible to the parent before output could be recovered; this review was rerun with a replacement read-only reviewer Pod.
|
||||||
|
|
||||||
|
Reviewer summary:
|
||||||
|
|
||||||
|
- The implementation adds `insomnia memory lint` as a headless mode in the existing user-facing `insomnia` binary.
|
||||||
|
- The memory lint path branches before raw terminal setup and Pod connection/spawn logic.
|
||||||
|
- Parser tests preserve `insomnia memory` as positional Pod name behavior.
|
||||||
|
- The collector targets summary, decisions, requests, and knowledge records while ignoring opaque memory subsystem directories and workflow files.
|
||||||
|
- Existing `memory::Linter` and `WriteMode::Update` are used, and the code only reads files / writes reports.
|
||||||
|
- Human and JSON outputs are deterministic enough for the ticket, and exit code mapping matches requirements.
|
||||||
|
|
||||||
|
Blockers: none.
|
||||||
|
|
||||||
|
Non-blocking follow-ups:
|
||||||
|
|
||||||
|
- Add broader fixture coverage for `_staging`, `_usage`, knowledge, and decisions if desired.
|
||||||
|
- Add process-level exit-code integration tests if a CLI test harness is introduced later.
|
||||||
|
|
||||||
|
Validation adequacy: coder-reported validation is sufficient for this ticket. Reviewer additionally checked `git diff --check develop...HEAD` read-only.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: hare at: 2026-05-31T02:15:16Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Main workspace validation after merge:
|
||||||
|
|
||||||
|
- `cargo fmt --check` passed
|
||||||
|
- `cargo test -p tui memory_lint -- --nocapture` passed (10 passed)
|
||||||
|
- `cargo test -p tui` passed (224 passed)
|
||||||
|
- `cargo check -p tui` passed with pre-existing dead-code warnings in `llm-worker` and `tui`
|
||||||
|
- `./tickets.sh doctor` passed
|
||||||
|
- `git diff --check` passed
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: close author: hare at: 2026-05-31T02:15:17Z status: closed -->
|
||||||
|
|
||||||
|
## Closed
|
||||||
|
|
||||||
|
Implemented `insomnia memory lint` as a headless command in the existing user-facing `insomnia` binary. The command lints workspace memory/knowledge records with the existing `memory::Linter` using `WriteMode::Update`, supports human and JSON output, handles warnings-as-errors, preserves `insomnia memory` as a positional Pod name, and returns before TUI/raw-terminal or Pod connection paths. External review approved and validation passed.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
id: 20260531-003743-codex-gpt55-effective-context-window
|
id: 20260531-003743-codex-gpt55-effective-context-window
|
||||||
slug: codex-gpt55-effective-context-window
|
slug: codex-gpt55-effective-context-window
|
||||||
title: Provider: make codex gpt-5.5 context window effective
|
title: Provider: make codex gpt-5.5 context window effective
|
||||||
status: open
|
status: closed
|
||||||
kind: task
|
kind: task
|
||||||
priority: P2
|
priority: P2
|
||||||
labels: [provider, models, catalog]
|
labels: [provider, models, catalog]
|
||||||
created_at: 2026-05-31T00:37:43Z
|
created_at: 2026-05-31T00:37:43Z
|
||||||
updated_at: 2026-05-31T00:38:23Z
|
updated_at: 2026-05-31T01:58:24Z
|
||||||
assignee: null
|
assignee: null
|
||||||
legacy_ticket: null
|
legacy_ticket: null
|
||||||
---
|
---
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Updated the builtin `codex-oauth/gpt-5.5` entry so `context_window = 272000` directly represents the effective Codex OAuth / ChatGPT backend route limit, with a catalog comment explaining that generic OpenAI docs advertise 1.05M. Removed catalog-level `max_context_window` from that entry while preserving inline/config `max_context_window` support. External review approved and validation passed.
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
<!-- event: create author: tickets.sh at: 2026-05-31T00:37:43Z -->
|
||||||
|
|
||||||
|
## Created
|
||||||
|
|
||||||
|
Created by tickets.sh create.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: plan author: hare at: 2026-05-31T00:38:23Z -->
|
||||||
|
|
||||||
|
## Plan
|
||||||
|
|
||||||
|
Implementation plan:
|
||||||
|
|
||||||
|
1. Update only the provider-specific builtin catalog entry for `codex-oauth/gpt-5.5`: set `context_window = 272000`, remove `max_context_window`, and add a clear comment about generic OpenAI docs vs Codex OAuth effective limit.
|
||||||
|
2. Keep `resources/profiles/default.lua` unchanged.
|
||||||
|
3. Adjust tests so builtin catalog expectations use effective `context_window`, while `max_context_window` behavior remains covered by an inline/config-specific test if the field remains supported.
|
||||||
|
4. Validate with focused provider/manifest tests plus `cargo fmt --check`, `./tickets.sh doctor`, and `git diff --check`.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: hare at: 2026-05-31T01:57:37Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Implementation report from coder Pod `codex-gpt55-context-coder-20260531`:
|
||||||
|
|
||||||
|
- Branch: `codex-gpt55-effective-context-window`
|
||||||
|
- Commit: `ed3e97c22a8e21dce9223114526932a11b268589` (`provider: use codex gpt55 effective context`)
|
||||||
|
- Updated `resources/models/builtin.toml` so `codex-oauth/gpt-5.5` records the effective Codex OAuth route limit directly as `context_window = 272000` and no longer has `max_context_window`.
|
||||||
|
- Added a nearby comment explaining that OpenAI documents GPT-5.5 as 1.05M context, but Codex OAuth / ChatGPT backend access is effectively limited around 272k.
|
||||||
|
- Kept default profile unchanged and did not change OpenRouter `openai/gpt-5.5`.
|
||||||
|
- Preserved `max_context_window` support for inline/config overrides and updated tests accordingly.
|
||||||
|
|
||||||
|
Validation reported by coder:
|
||||||
|
|
||||||
|
- `cargo fmt --check` passed
|
||||||
|
- `cargo test -p provider catalog` passed
|
||||||
|
- `cargo test -p manifest model` passed
|
||||||
|
- `cargo check -p provider -p manifest` passed
|
||||||
|
- `./tickets.sh doctor` passed
|
||||||
|
- `git diff --check` passed
|
||||||
|
|
||||||
|
Unresolved issues: none.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: review author: hare at: 2026-05-31T01:57:38Z status: approve -->
|
||||||
|
|
||||||
|
## Review: approve
|
||||||
|
|
||||||
|
External review by reviewer Pod `codex-gpt55-context-reviewer-20260531`: approve.
|
||||||
|
|
||||||
|
Reviewer summary:
|
||||||
|
|
||||||
|
- The change is correctly scoped to `codex-oauth/gpt-5.5` and relevant tests.
|
||||||
|
- `context_window = 272000` now represents the effective Codex OAuth route limit directly.
|
||||||
|
- The comment explains the distinction between OpenAI's advertised 1.05M context window and the observed/known Codex OAuth effective limit.
|
||||||
|
- `max_context_window` support remains available for inline/config cases.
|
||||||
|
- Default profile and OpenRouter `openai/gpt-5.5` were not changed.
|
||||||
|
|
||||||
|
Blockers: none.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: hare at: 2026-05-31T01:58:24Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Main workspace validation after merge:
|
||||||
|
|
||||||
|
- `cargo fmt --check` passed
|
||||||
|
- `cargo test -p provider catalog` passed
|
||||||
|
- `cargo test -p manifest model` passed
|
||||||
|
- `cargo check -p provider -p manifest` passed
|
||||||
|
- `./tickets.sh doctor` passed
|
||||||
|
- `git diff --check` passed
|
||||||
|
|
||||||
|
Warnings observed are pre-existing dead-code warnings in `llm-worker` / filtered manifest test helpers.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: close author: hare at: 2026-05-31T01:58:24Z status: closed -->
|
||||||
|
|
||||||
|
## Closed
|
||||||
|
|
||||||
|
Updated the builtin `codex-oauth/gpt-5.5` entry so `context_window = 272000` directly represents the effective Codex OAuth / ChatGPT backend route limit, with a catalog comment explaining that generic OpenAI docs advertise 1.05M. Removed catalog-level `max_context_window` from that entry while preserving inline/config `max_context_window` support. External review approved and validation passed.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
<!-- event: migration author: tickets.sh-migration at: 2026-05-27T00:00:19Z -->
|
|
||||||
|
|
||||||
## Migrated
|
|
||||||
|
|
||||||
Migrated from TODO.md entry without a legacy ticket file. No legacy review file was present at migration time.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- event: plan author: hare at: 2026-05-31T00:51:55Z -->
|
|
||||||
|
|
||||||
## Plan
|
|
||||||
|
|
||||||
Planning note:
|
|
||||||
|
|
||||||
- Keep this in the existing user-facing `insomnia` binary implemented by the `tui` crate. Do not add another installed command for this ticket.
|
|
||||||
- The command should be headless: parse args, lint files, print report, exit. It must not initialize terminal UI or connect to a Pod.
|
|
||||||
- `insomnia memory lint` is preferred, but `insomnia memory` alone should continue to be a valid Pod-name attach/create path if practical with the current parser.
|
|
||||||
- Use `memory::Linter` directly so CLI behavior tracks tool pre-write validation. Existing files should be linted with `WriteMode::Update`.
|
|
||||||
- Keep crate rename / single-binary architecture as future cleanup, not part of this ticket.
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
<!-- event: create author: tickets.sh at: 2026-05-31T00:37:43Z -->
|
|
||||||
|
|
||||||
## Created
|
|
||||||
|
|
||||||
Created by tickets.sh create.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- event: plan author: hare at: 2026-05-31T00:38:23Z -->
|
|
||||||
|
|
||||||
## Plan
|
|
||||||
|
|
||||||
Implementation plan:
|
|
||||||
|
|
||||||
1. Update only the provider-specific builtin catalog entry for `codex-oauth/gpt-5.5`: set `context_window = 272000`, remove `max_context_window`, and add a clear comment about generic OpenAI docs vs Codex OAuth effective limit.
|
|
||||||
2. Keep `resources/profiles/default.lua` unchanged.
|
|
||||||
3. Adjust tests so builtin catalog expectations use effective `context_window`, while `max_context_window` behavior remains covered by an inline/config-specific test if the field remains supported.
|
|
||||||
4. Validate with focused provider/manifest tests plus `cargo fmt --check`, `./tickets.sh doctor`, and `git diff --check`.
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
---
|
||||||
|
id: 20260531-005557-single-binary-insomnia-cli
|
||||||
|
slug: single-binary-insomnia-cli
|
||||||
|
title: CLI: clarify single-binary insomnia architecture
|
||||||
|
status: open
|
||||||
|
kind: task
|
||||||
|
priority: P2
|
||||||
|
labels: [cli, architecture, nix]
|
||||||
|
created_at: 2026-05-31T00:55:57Z
|
||||||
|
updated_at: 2026-05-31T00:56:38Z
|
||||||
|
assignee: null
|
||||||
|
legacy_ticket: null
|
||||||
|
---
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
The installed user-facing command is already `insomnia`, while the Cargo package/crate that owns it is still named `tui`. Headless CLI commands such as `insomnia memory lint` are starting to live in that same binary. This is acceptable short-term, but the architecture should be made explicit before more commands accumulate.
|
||||||
|
|
||||||
|
The preferred product direction is a single user-facing `insomnia` binary that can run TUI screens and headless commands. The main downside is that headless commands inherit ratatui/crossterm and related dependencies, but binary size is not expected to dominate runtime memory compared with LLM clients, HTTP/TLS, JSON/session/memory processing, and spawned process orchestration. A single binary also improves distribution and “minimal standalone install” ergonomics.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Decide and document the intended command architecture:
|
||||||
|
- one installed `insomnia` binary as the primary user-facing entry point;
|
||||||
|
- headless subcommands under that binary;
|
||||||
|
- TUI/interactive paths as subcommands or default modes;
|
||||||
|
- relationship to `insomnia-pod`.
|
||||||
|
- Refactor only if necessary to keep headless commands cleanly separated from terminal/TUI initialization.
|
||||||
|
- Ensure headless commands do not initialize ratatui, enter raw mode, connect Pod sockets, or spawn Pod processes unless explicitly requested by that command.
|
||||||
|
- Evaluate whether to rename the Cargo package/crate from `tui` to `insomnia`.
|
||||||
|
- If the rename is straightforward, implement it with scoped Nix/docs updates.
|
||||||
|
- If it is broad/risky, leave the rename as a follow-up and document the boundary.
|
||||||
|
- Keep installed command names stable unless a separate explicit decision says otherwise.
|
||||||
|
- Preserve Nix/Home Manager packaging expectations: installing the flake package should put the intended commands on `PATH`.
|
||||||
|
- Avoid introducing separate binaries for every headless command unless there is a measured startup/binary-size/runtime-memory reason.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Reworking the Pod daemon binary protocol.
|
||||||
|
- Removing `insomnia-pod` unless a separate architecture decision covers Pod process lifecycle.
|
||||||
|
- Feature-gating ratatui/crossterm for a headless-only build before there is a measured need.
|
||||||
|
- Large CLI UX redesign beyond clarifying the single-binary structure.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- The repository has a clear documented decision or implementation path for the single-binary `insomnia` CLI architecture.
|
||||||
|
- Existing TUI flows and headless command flows remain tested.
|
||||||
|
- If crate/package rename is implemented, Cargo/Nix/docs references are updated consistently.
|
||||||
|
- If crate/package rename is deferred, a precise follow-up or documented rationale explains why.
|
||||||
|
- `cargo fmt --check`, relevant `cargo test`/`cargo check`, `./tickets.sh doctor`, and `git diff --check` pass.
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<!-- event: create author: tickets.sh at: 2026-05-31T00:55:57Z -->
|
||||||
|
|
||||||
|
## Created
|
||||||
|
|
||||||
|
Created by tickets.sh create.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: decision author: hare at: 2026-05-31T00:56:38Z -->
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Decision note from discussion:
|
||||||
|
|
||||||
|
- Short-term direction: keep adding headless commands to the existing user-facing `insomnia` binary owned by the current `tui` crate.
|
||||||
|
- Product preference: a single standalone `insomnia` binary is easier to distribute and explain than many small binaries.
|
||||||
|
- Known tradeoff: headless commands inherit TUI dependencies such as ratatui/crossterm. This is acceptable until binary size/startup/runtime memory is measured as a real problem.
|
||||||
|
- Internal structure should still separate headless command dispatch from terminal/TUI initialization.
|
||||||
|
- Future cleanup: consider renaming the Cargo package/crate from `tui` to `insomnia`; treat it as part of this ticket only if the scope remains contained.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
---
|
||||||
|
id: 20260531-010005-plugin-extension-surface
|
||||||
|
slug: plugin-extension-surface
|
||||||
|
title: Plugin: define extension surface for hooks and tools
|
||||||
|
status: open
|
||||||
|
kind: feature
|
||||||
|
priority: P2
|
||||||
|
labels: [plugin, hooks, tools, wasm, mcp]
|
||||||
|
created_at: 2026-05-31T01:00:05Z
|
||||||
|
updated_at: 2026-05-31T01:01:09Z
|
||||||
|
assignee: null
|
||||||
|
legacy_ticket: null
|
||||||
|
---
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
insomnia currently has internal Hook / Tool concepts, plus a separate planned MCP integration ticket (`mcp-integration`). The next design step is to define the project-level Plugin surface: how user/project-provided extensions can contribute Tools and Hooks without weakening scope, permission, history, or prompt-context invariants.
|
||||||
|
|
||||||
|
The plugin surface should not be a grab bag of arbitrary code execution. Candidate extension mechanisms have different trust and protocol properties:
|
||||||
|
|
||||||
|
- MCP: protocol-bound external tool/resource/prompt provider surface.
|
||||||
|
- TOML/config-only hooks: declarative configuration for simple hook behavior without arbitrary code.
|
||||||
|
- WASM: planned first programmable plugin runtime for Hooks and Tools, with explicit capability imports and sandboxing.
|
||||||
|
- General scripting languages: considered, but not the initial direction because arbitrary script execution broadens the trust/runtime surface too quickly.
|
||||||
|
|
||||||
|
## Related work
|
||||||
|
|
||||||
|
- `work-items/open/20260529-161928-mcp-integration/` — MCP integration as one plugin backend / external capability bridge.
|
||||||
|
- Existing internal hooks/tools code: `crates/pod`, `crates/tools`, `crates/llm-worker`.
|
||||||
|
- Manifest permission policy and scope enforcement must remain authoritative for plugin-provided tools.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Define a Plugin surface that can provide:
|
||||||
|
- Tools callable by the LLM through the normal ToolRegistry / permission / scope path;
|
||||||
|
- Hooks observing or influencing Pod/Worker lifecycle through the existing Hook boundary, not by directly mutating worker history/context.
|
||||||
|
- Separate plugin description/registration from plugin runtime implementation.
|
||||||
|
- A plugin manifest should declare provided tools/hooks, required capabilities, configuration schema or config values, and trust/runtime type.
|
||||||
|
- Runtime implementations can include MCP, declarative config hooks, and WASM in separate phases.
|
||||||
|
- Keep MCP as a related backend, not the whole plugin model.
|
||||||
|
- MCP servers remain untrusted external capability providers bridged through allowlists, bounded output, scope/permission policy, and explicit resource/prompt use.
|
||||||
|
- Define a declarative hook path for simple TOML/config-only behavior where code execution is unnecessary.
|
||||||
|
- Define a WASM plugin direction for programmable Hooks/Tools.
|
||||||
|
- WASM modules must receive explicit host imports/capabilities only.
|
||||||
|
- File/network/process access must not be ambient; all external effects go through host-provided capability APIs and existing policy checks.
|
||||||
|
- Tool outputs must be bounded and recorded through normal history/tool-result paths.
|
||||||
|
- Preserve LLM context/history invariants.
|
||||||
|
- Plugins must not inject cross-turn invisible context.
|
||||||
|
- If plugin output becomes model-visible, it must enter through durable history/tool/hook paths according to existing rules.
|
||||||
|
- Preserve scope and permission invariants.
|
||||||
|
- Plugin-provided tools must not bypass `ScopedFs`, manifest tool permission policy, child scope delegation, or web/network policy.
|
||||||
|
- Clarify trust model and lifecycle.
|
||||||
|
- Builtin vs project vs user plugins.
|
||||||
|
- Discovery/enablement through manifest/profile/config.
|
||||||
|
- Versioning / compatibility boundaries.
|
||||||
|
- Diagnostics when a plugin cannot load or asks for unavailable capabilities.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Implementing the full WASM runtime in the first design step.
|
||||||
|
- Implementing MCP itself beyond referencing the existing MCP integration ticket.
|
||||||
|
- Supporting arbitrary host scripting languages as a first-class plugin runtime.
|
||||||
|
- Allowing plugins to mutate session history, memory, prompt context, or scope outside approved APIs.
|
||||||
|
- Adding UI plugin systems or TUI rendering extensions.
|
||||||
|
|
||||||
|
## Suggested phases
|
||||||
|
|
||||||
|
1. **Design / architecture note**
|
||||||
|
- Define Plugin, PluginManifest, PluginRuntimeKind, Tool contribution, Hook contribution, capability request, and trust/source model.
|
||||||
|
- Map MCP, declarative hooks, and WASM onto that model.
|
||||||
|
2. **Internal registry boundary**
|
||||||
|
- Introduce code structure that can register plugin-provided tool/hook descriptors without changing runtime behavior yet.
|
||||||
|
3. **Declarative hooks MVP**
|
||||||
|
- Add a non-code configuration path for simple hook behavior if an immediate use case exists.
|
||||||
|
4. **WASM spike**
|
||||||
|
- Evaluate runtime (`wasmtime` or alternative), host imports, resource limits, serialization, and Nix/package impact.
|
||||||
|
5. **MCP bridge alignment**
|
||||||
|
- Ensure `mcp-integration` plugs into the same Tool/permission/output boundary rather than becoming a parallel extension path.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- The repository has a documented plugin architecture proposal covering Tools, Hooks, runtimes, capability model, trust model, and discovery/enablement.
|
||||||
|
- MCP is positioned as one plugin backend / bridge and linked to `mcp-integration`, not treated as the only extension mechanism.
|
||||||
|
- The proposal explicitly explains why arbitrary scripting languages are deferred and why WASM is the initial programmable runtime direction.
|
||||||
|
- The design preserves existing scope, permission, history, and prompt-context invariants.
|
||||||
|
- Follow-up implementation tickets can be cut independently for declarative hooks, WASM runtime, and MCP bridge integration.
|
||||||
|
- Any code changes in this ticket, if taken beyond design docs, are limited to safe internal boundaries and have focused tests.
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<!-- event: create author: tickets.sh at: 2026-05-31T01:00:05Z -->
|
||||||
|
|
||||||
|
## Created
|
||||||
|
|
||||||
|
Created by tickets.sh create.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: decision author: hare at: 2026-05-31T01:01:09Z -->
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Initial decision note from user discussion:
|
||||||
|
|
||||||
|
- The plugin surface should mainly expose Hooks and Tools.
|
||||||
|
- MCP is related, but should be treated as one protocol-bound backend/bridge rather than the entire plugin model.
|
||||||
|
- Planned plugin mechanisms:
|
||||||
|
- MCP for protocol-constrained external capability providers;
|
||||||
|
- TOML/config-only hooks for simple behavior that does not need arbitrary code;
|
||||||
|
- WASM for programmable Hooks/Tools with explicit host capabilities.
|
||||||
|
- General scripting languages were considered, but the initial direction is WASM because it offers a clearer sandbox/capability boundary.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
---
|
||||||
|
id: 20260531-022821-pod-tool-surface-restore-list
|
||||||
|
slug: pod-tool-surface-restore-list
|
||||||
|
title: Pod tools: unify pod listing and rename restore operation
|
||||||
|
status: open
|
||||||
|
kind: task
|
||||||
|
priority: P2
|
||||||
|
labels: [pod, tools, orchestration]
|
||||||
|
created_at: 2026-05-31T02:28:21Z
|
||||||
|
updated_at: 2026-05-31T02:29:17Z
|
||||||
|
assignee: null
|
||||||
|
legacy_ticket: null
|
||||||
|
---
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
The current LLM-callable Pod tool surface has overlapping concepts:
|
||||||
|
|
||||||
|
- `ListPods` is registry-oriented and lists Pods spawned by the current Pod.
|
||||||
|
- `ListVisiblePods` is state/visibility-oriented and lists visible live/stored Pods.
|
||||||
|
- `InspectPod` provides a named detail lookup.
|
||||||
|
- `AttachOrRestorePod` has an ambiguous name: the "attach" branch mostly detects an existing live socket and returns connection information; it does not clearly mean that the target becomes a comm-registered child for `SendToPod` / `ReadPodOutput`.
|
||||||
|
|
||||||
|
Recent multi-agent workflow incidents also showed that stopped/restorable child Pods and missing/unreachable children are not clearly represented. For LLM tool use, fewer, more precise operations are preferable.
|
||||||
|
|
||||||
|
User decision:
|
||||||
|
|
||||||
|
- Integrate `ListVisiblePods` semantics into `ListPods`.
|
||||||
|
- Remove `InspectPod` from the LLM tool surface.
|
||||||
|
- Rename `AttachOrRestorePod` to `RestorePod`.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Replace the old `ListPods` result semantics with the state/visibility-aware listing currently represented by `ListVisiblePods`.
|
||||||
|
- The tool should list Pods visible to the current Pod, not host-wide Pods.
|
||||||
|
- Visible set should include self and spawned children according to current visibility rules.
|
||||||
|
- Preserve enough fields to distinguish live/reachable, stored/restorable, missing/corrupt/unrestorable, and comm/registry state where available.
|
||||||
|
- If preserving exact `PodList`/`PodListEntry` structures is easier, use them; do not keep two nearly identical listing tools.
|
||||||
|
- Remove `ListVisiblePods` as a separate LLM-callable tool.
|
||||||
|
- Update tool registration, tool descriptions, tests, prompts/docs if they mention it.
|
||||||
|
- If protocol enum compatibility must remain for non-LLM/TUI callers, keep internal compatibility only and do not expose it as an LLM tool; otherwise remove it cleanly.
|
||||||
|
- Remove `InspectPod` from the LLM-callable tool surface.
|
||||||
|
- Update tool registration, tool descriptions, schema, tests, prompts/docs.
|
||||||
|
- Prefer `ListPods` for discovery/detail and `RestorePod` for action.
|
||||||
|
- If protocol enum compatibility must remain, keep it internal/non-advertised only; otherwise remove it cleanly.
|
||||||
|
- Rename `AttachOrRestorePod` to `RestorePod` in the LLM-callable surface.
|
||||||
|
- The operation should restore a visible stopped/restorable Pod, or report that a visible Pod is already live.
|
||||||
|
- Avoid using “attach” terminology in tool name/description unless a real comm-registry materialization semantics is implemented.
|
||||||
|
- Update tool schema, descriptions, registration, tests, and prompts/docs.
|
||||||
|
- Decide whether to keep a backwards-compatible protocol alias internally; avoid exposing both names to the LLM.
|
||||||
|
- Preserve scope, visibility, and safety boundaries.
|
||||||
|
- Do not make host-wide Pod enumeration available.
|
||||||
|
- Do not allow restoring Pods outside the caller’s visibility set.
|
||||||
|
- Do not broaden delegated scope handling in this ticket.
|
||||||
|
- Keep behavior for `SpawnPod`, `SendToPod`, `ReadPodOutput`, and `StopPod` coherent with the new listing/restore tools.
|
||||||
|
- If full stopped/restorable child semantics require a larger registry change, document it as follow-up rather than half-implementing it.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Full redesign of stopped/restorable child lifecycle and delegated scope reclaim semantics.
|
||||||
|
- Changing Pod registry durable authority or parent restore pruning behavior.
|
||||||
|
- Host-wide Pod management tools.
|
||||||
|
- TUI dashboard redesign.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- The LLM-visible tool list exposes `ListPods` and `RestorePod`, and no longer exposes `ListVisiblePods`, `InspectPod`, or `AttachOrRestorePod`.
|
||||||
|
- `ListPods` returns visibility/state-aware data sufficient for the LLM to decide whether a Pod is live, stopped/restorable, or unavailable.
|
||||||
|
- `RestorePod` replaces the old `AttachOrRestorePod` name in tool descriptions and tests.
|
||||||
|
- Existing tests are updated and new/renamed tests cover:
|
||||||
|
- `ListPods` includes visible stored/live entries formerly covered by `ListVisiblePods`;
|
||||||
|
- `RestorePod` rejects invisible/missing Pods;
|
||||||
|
- `RestorePod` reports already-live visible Pods without spawning another process;
|
||||||
|
- removed tools are not registered in the LLM tool registry.
|
||||||
|
- Any intentional protocol compatibility aliases are documented in code comments and are not advertised to the LLM.
|
||||||
|
- `cargo fmt --check`, focused Pod/tools tests, `cargo check` for affected crates, `./tickets.sh doctor`, and `git diff --check` pass.
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!-- event: create author: tickets.sh at: 2026-05-31T02:28:21Z -->
|
||||||
|
|
||||||
|
## Created
|
||||||
|
|
||||||
|
Created by tickets.sh create.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: decision author: hare at: 2026-05-31T02:29:17Z -->
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Decision note:
|
||||||
|
|
||||||
|
- `ListVisiblePods` should be folded into `ListPods` rather than kept as a separate tool.
|
||||||
|
- `InspectPod` is not needed as an LLM-facing tool; `ListPods` should provide enough state/detail, and action should go through restore/send/read/stop tools.
|
||||||
|
- `AttachOrRestorePod` should become `RestorePod`. The current "attach" branch only observes an already-live socket and returns status/socket data; it does not mean a persistent attachment or comm-registry materialization, so the name is misleading.
|
||||||
|
- If deeper semantics are needed later, define them explicitly as comm-registration/materialization rather than using "attach" implicitly.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
Loading…
Reference in New Issue
Block a user