app
AI Flow
Public Made by Adomby adom
Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board.
← Commit history
AI Flow 0.2.75: timeline off Parts Search's port (#201)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
8 files changed
+56−17
Cargo.lock+10−10@@ -4,7 +4,7 @@ version = 4 [[package]] name = "adom-aiflow"-version = "0.2.74"+version = "0.2.75" dependencies = [ "aiflow-analyze", "aiflow-board",@@ -25,7 +25,7 @@ dependencies = [ [[package]] name = "aiflow-analyze"-version = "0.2.74"+version = "0.2.75" dependencies = [ "aiflow-board", "serde",@@ -35,7 +35,7 @@ dependencies = [ [[package]] name = "aiflow-board"-version = "0.2.74"+version = "0.2.75" dependencies = [ "roxmltree", "serde",@@ -44,7 +44,7 @@ dependencies = [ [[package]] name = "aiflow-bridge"-version = "0.2.74"+version = "0.2.75" dependencies = [ "aiflow-board", "serde",@@ -53,7 +53,7 @@ dependencies = [ [[package]] name = "aiflow-copper"-version = "0.2.74"+version = "0.2.75" dependencies = [ "aiflow-board", "aiflow-grid",@@ -63,7 +63,7 @@ dependencies = [ [[package]] name = "aiflow-grid"-version = "0.2.74"+version = "0.2.75" dependencies = [ "aiflow-board", "serde",@@ -72,7 +72,7 @@ dependencies = [ [[package]] name = "aiflow-place"-version = "0.2.74"+version = "0.2.75" dependencies = [ "aiflow-board", "serde",@@ -81,7 +81,7 @@ dependencies = [ [[package]] name = "aiflow-pours"-version = "0.2.74"+version = "0.2.75" dependencies = [ "aiflow-board", "aiflow-copper",@@ -91,7 +91,7 @@ dependencies = [ [[package]] name = "aiflow-router"-version = "0.2.74"+version = "0.2.75" dependencies = [ "aiflow-board", "aiflow-grid",@@ -101,7 +101,7 @@ dependencies = [ [[package]] name = "aiflow-run"-version = "0.2.74"+version = "0.2.75" dependencies = [ "serde", "serde_json",
Cargo.toml+1−1@@ -14,7 +14,7 @@ members = [ ] [workspace.package]-version = "0.2.74"+version = "0.2.75" edition = "2021" license = "MIT" repository = "https://wiki.adom.inc/adom/adom-aiflow"
bin/adom-aiflow⋯ 1 unchanged line ⋯
crates/adom-aiflow/src/dashboard.rs+28−2@@ -388,8 +388,24 @@ fn quote_dir(dir: &str) -> String { if dir.chars().all(|c| c.is_alphanumeric() || "/._-".contains(c)) { dir.to_string() } else { format!("'{}'", dir.replace('\'', "'\\''")) } } +/// Issue #201 (Glenn): the dashboard's ports, clear of the 88xx ports Adom's dock apps take (Parts Search 8893,+/// Fields 8874, ...). A run that saved a port of the old range does not take it again.+pub const PORTS: std::ops::Range<u16> = 18893..18993;+const OLD_PORTS: std::ops::Range<u16> = 8893..8993;++/// The ports to try, in order: an asked-for port, the run's saved port when it is in the range, then the range.+pub fn port_candidates(requested: Option<u16>, saved: Option<u16>) -> Vec<u16> {+ requested.into_iter().chain(saved.filter(|p| PORTS.contains(p))).chain(PORTS).collect()+}+ fn start_server(dir: &Path, tdir: &Path, id: &str, requested: Option<u16>, saved: Option<u16>) -> Result<u16, String> {- for candidate in requested.into_iter().chain(saved).chain(8893..8993) {+ // this run's own server still on an old-range port (the one Parts Search wants): stop it, verified as ours+ if let Some(old) = saved.filter(|p| OLD_PORTS.contains(p) && requested != Some(*p)) {+ if identity(old).as_deref() == Some(id) {+ let _ = stop_server(&serde_json::json!({"port": old, "runId": id, "runDir": dir.to_string_lossy()}));+ }+ }+ for candidate in port_candidates(requested, saved) { if identity(candidate).as_deref() == Some(id) { return Ok(candidate); }@@ -417,7 +433,7 @@ fn start_server(dir: &Path, tdir: &Path, id: &str, requested: Option<u16>, saved // Do not kill a process by port: it might belong to another run. return Err(format!("dashboard server did not become ready on {candidate}; inspect {}", tdir.join("serve.log").display())); }- Err("no free dashboard port in 8893..8993".into())+ Err(format!("no free dashboard port in {}..{}", PORTS.start, PORTS.end)) } /// Show this run in its own Hydrogen tab, or say why not. Never opens a second tab for a thread that@@ -772,4 +788,14 @@ mod tests { assert!(!reusable(&json!({"finished": false, "port": 1, "runId": "x", "lastActive": now}))); assert!(describe(&json!({"name": "AI Flow \u{b7} B", "runDir": "/r", "lastActive": now})).contains("just now")); }+ /// Issue #201: never the 88xx ports Adom's dock apps use, unless asked for.+ #[test]+ fn the_dashboard_stays_off_the_dock_apps_ports() {+ let c = port_candidates(None, Some(8893));+ assert_eq!(c[0], 18893, "a saved old-range port is not taken again: {:?}", &c[..3]);+ assert!(c.iter().all(|p| *p >= 18893));+ assert_eq!(port_candidates(None, Some(18900))[0], 18900, "a saved new-range port is reused");+ assert_eq!(port_candidates(Some(8895), Some(8893))[0], 8895, "an explicit --port is honoured");+ }+ }
crates/adom-aiflow/src/main.rs+6−2@@ -158,7 +158,7 @@ enum Cmd { /// `timeline list`: every AI Flow tab (registry ~/.adom/aiflow/dashboards.json reconciled with Hydrogen): name, board, lane, AI thread, run, step, finished, server, last active; AI Flow tabs not in the registry show as unknown. /// `timeline close --run R | --name N | --finished | --stale`: remove only AI Flow tabs and stop their own verified servers. /// One tab per run: `open --reuse-tab "<name>"` shows this run in another run's AI Flow tab (renamed in place); `open --new-tab --why "<reason>"` opens a second tab for the same thread.- Timeline { #[arg(value_parser = ["open", "serve", "write", "present", "list", "close"])] action: String, #[arg(long, default_value_t = 8893)] port: u16, #[arg(long)] json: Option<String>,+ Timeline { #[arg(value_parser = ["open", "serve", "write", "present", "list", "close"])] action: String, #[arg(long, default_value_t = 18893)] port: u16, #[arg(long)] json: Option<String>, /// open: show this run in that AI Flow tab instead of a new one (the tab is renamed in place) #[arg(long)] reuse_tab: Option<String>, /// open: a second AI Flow tab for this thread (needs --why) #[arg(long)] new_tab: bool, /// open --new-tab: why the human needs this run on screen beside another (recorded in the registry)@@ -6608,7 +6608,11 @@ fn main() { match action.as_str() { "write" => ok(&format!("timeline written: step {} of {}, {} done", tl["position"], tl["total"], tl["done"]), &[]), "serve" => {- timeline_server::serve(&tdir,*port).unwrap_or_else(|e|err(&e,&["Use the existing run server; do not create another dashboard.".into()]));+ timeline_server::serve(&tdir,*port).unwrap_or_else(|e|{+ // issue #201: say who holds the port, so nobody has to hunt for it+ let holder=std::process::Command::new("ss").args(["-ltnpH",&format!("sport = :{port}")]).output().ok().map(|o|String::from_utf8_lossy(&o.stdout).to_string()).and_then(|t|{let i=t.find("users:((\"")?+9;let rest=&t[i..];let name=&rest[..rest.find('"')?];let pid=rest.find("pid=").map(|j|rest[j+4..].chars().take_while(|c|c.is_ascii_digit()).collect::<String>()).unwrap_or_default();Some(format!("{name} (pid {pid})"))});+ err(&format!("port {port}: {e}{}",holder.map(|h|format!("; it is held by {h}")).unwrap_or_default()),&[format!("Pass another port: `timeline serve --port <free port>` (AI Flow's own dashboards use {}..{}, clear of the 88xx ports Adom's dock apps take, such as Parts Search on 8893).",dashboard::PORTS.start,dashboard::PORTS.end),"Use the existing run server; do not create another dashboard.".into()])+ }); } "present" => { let caller=thread(&cli);
docs/release-0.2.75.mdadded+9@@ -0,0 +1,9 @@+# AI Flow 0.2.75++- **#201 (Glenn): the run's timeline no longer takes Parts Search's port.** The dashboard server used 8893..8993,+ and 8893 is the port Adom Parts Search's dock app uses, so with a run open the Parts Search card could not+ start. AI Flow's dashboards now use 18893..18993, clear of the 88xx ports Adom's dock apps take, and+ `timeline serve` defaults to 18893. A run that saved a port of the old range does not take it again: when its+ own server is still there (verified as this run's), it is stopped and started in the new range, and the run's+ tab follows the new URL. An asked-for `--port` is honoured; when it is taken, the error names the program+ holding it and suggests another.
package.json+1−1@@ -1,7 +1,7 @@ { "slug": "adom-aiflow", "type": "app",- "version": "0.2.74",+ "version": "0.2.75", "title": "AI Flow", "description": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. One Rust binary with a crate per step (placement helpers, a grid router with Kelvin taps, pours with keepouts, KiCad's DRC gate, live landing through the KiCad Bridge, copper measurement, current and thermal analysis) and a finish line that refuses an unfinished board. Every command answers with hints for the AI; every turn, its thinking time and every rework loop go into run.jsonl, so Claude, Codex and any other engine are compared on the same flow. KiCad, Fusion 360 and Altium Designer today (the same steps and gates through each tool's bridge); Adom's own web apps next.", "summary": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. The AI thinks its way from placement through routing, pours, current and thermal analysis to a delivered video; the binary does the fast, deterministic parts of every step, hands back hints, and keeps a ledger of every turn, every return to an earlier step, and the clock from the prompt to done.",
page.json+1−1@@ -1,7 +1,7 @@ { "slug": "adom-aiflow", "type": "app",- "version": "0.2.74",+ "version": "0.2.75", "title": "AI Flow", "description": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. One Rust binary with a crate per step (placement helpers, a grid router with Kelvin taps, pours with keepouts, KiCad's DRC gate, live landing through the KiCad Bridge, copper measurement, current and thermal analysis) and a finish line that refuses an unfinished board. Every command answers with hints for the AI; every turn, its thinking time and every rework loop go into run.jsonl, so Claude, Codex and any other engine are compared on the same flow. KiCad, Fusion 360 and Altium Designer today (the same steps and gates through each tool's bridge); Adom's own web apps next.", "summary": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. The AI thinks its way from placement through routing, pours, current and thermal analysis to a delivered video; the binary does the fast, deterministic parts of every step, hands back hints, and keeps a ledger of every turn, every return to an earlier step, and the clock from the prompt to done.",