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
Publish 0.2.39
6 files changed
+31−17
Cargo.lock+10−10@@ -4,7 +4,7 @@ version = 4 [[package]] name = "adom-aiflow"-version = "0.2.38"+version = "0.2.39" dependencies = [ "aiflow-analyze", "aiflow-board",@@ -25,7 +25,7 @@ dependencies = [ [[package]] name = "aiflow-analyze"-version = "0.2.38"+version = "0.2.39" dependencies = [ "aiflow-board", "serde",@@ -35,7 +35,7 @@ dependencies = [ [[package]] name = "aiflow-board"-version = "0.2.38"+version = "0.2.39" dependencies = [ "roxmltree", "serde",@@ -44,7 +44,7 @@ dependencies = [ [[package]] name = "aiflow-bridge"-version = "0.2.38"+version = "0.2.39" dependencies = [ "aiflow-board", "serde",@@ -53,7 +53,7 @@ dependencies = [ [[package]] name = "aiflow-copper"-version = "0.2.38"+version = "0.2.39" dependencies = [ "aiflow-board", "aiflow-grid",@@ -63,7 +63,7 @@ dependencies = [ [[package]] name = "aiflow-grid"-version = "0.2.38"+version = "0.2.39" dependencies = [ "aiflow-board", "serde",@@ -72,7 +72,7 @@ dependencies = [ [[package]] name = "aiflow-place"-version = "0.2.38"+version = "0.2.39" dependencies = [ "aiflow-board", "serde",@@ -81,7 +81,7 @@ dependencies = [ [[package]] name = "aiflow-pours"-version = "0.2.38"+version = "0.2.39" dependencies = [ "aiflow-board", "aiflow-copper",@@ -91,7 +91,7 @@ dependencies = [ [[package]] name = "aiflow-router"-version = "0.2.38"+version = "0.2.39" dependencies = [ "aiflow-board", "aiflow-grid",@@ -101,7 +101,7 @@ dependencies = [ [[package]] name = "aiflow-run"-version = "0.2.38"+version = "0.2.39" dependencies = [ "serde", "serde_json",
Cargo.toml+1−1@@ -14,7 +14,7 @@ members = [ ] [workspace.package]-version = "0.2.38"+version = "0.2.39" edition = "2021" license = "MIT" repository = "https://wiki.adom.inc/adom/adom-aiflow"
bin/adom-aiflow⋯ 1 unchanged line ⋯
crates/adom-aiflow/src/main.rs+18−4@@ -5597,6 +5597,8 @@ fn main() { let mut open_err = None; let mut hwnd: Option<i64> = None; // one more try in a fresh session when the first window never shows (a transient pup miss, 2026-10-20)+ // issue #123: macOS pup keeps its window off-screen, where the browser stops painting it+ let mut offscreen = false; for attempt in 0..2 { if attempt > 0 { let _ = br.call("pup_close_window", &json!({"sessionId": sid})); } sid = format!("aiflow-fields-{}{}", now().chars().filter(|c| c.is_ascii_digit()).skip(8).collect::<String>(), if attempt > 0 { "-retry" } else { "" });@@ -5623,7 +5625,10 @@ fn main() { let rows = inner["data"]["windows"].as_array().or_else(|| inner["windows"].as_array()).cloned().unwrap_or_default(); let named: Vec<_> = rows.iter().filter(|w| w["title"].as_str().is_some_and(|t| t.starts_with("Adom Fields"))).collect(); let all_ours = sessions.map(|r| r.iter().filter(|w| w["title"].as_str() == Some("Adom Fields")).count()).unwrap_or(0) == 1;- if named.len() == 1 && all_ours { hwnd = named[0]["hwnd"].as_i64().filter(|h| *h > 0); }+ if named.len() == 1 && all_ours {+ hwnd = named[0]["hwnd"].as_i64().filter(|h| *h > 0);+ offscreen = named[0]["isOnscreen"] == json!(false);+ } } if hwnd.is_some() { break; } }@@ -5647,6 +5652,9 @@ fn main() { std::thread::sleep(std::time::Duration::from_secs(4)); // issue #98: the walkthrough take is capped in length as well as in bit rate if !clipwatch::recorder(&mut r, &br).0 { r.save().unwrap(); err(&format!("no walkthrough clip: {}", clipwatch::no_recorder_note(&r.data).unwrap_or_default()), &[]); }+ // an off-screen window records nothing (#123): put it on screen for the take, then tuck it away+ let raised = offscreen && Bridge::ok(&br.call("pup_raise_os_window", &json!({"sessionId": sid, "reason": "aiflow fields walkthrough: an off-screen window is not painted, so it cannot be filmed; lowered again when the take ends"})));+ if offscreen { eprintln!("{}", if raised { "the Fields window was off-screen (macOS): raised it on screen for the walkthrough take; it is lowered when the take ends" } else { "WARNING: the Fields window is off-screen (macOS) and pup could not raise it: the take will likely be empty" }); std::thread::sleep(std::time::Duration::from_secs(3)); } let rid = br.record_window_start_capped(win, "aiflow fields walkthrough", FIELDS_CLIP_MAX_MS).unwrap_or_else(|rep| err(&format!("recording of the app window did not start: {}", rep["errorCode"].as_str().unwrap_or("?")), &[])); r.data["captures"].as_array_mut().unwrap().push(json!({"recordingId": rid, "start": now(), "kind": "window", "hwnd": win, "step": step, "visit": visit, "back": false, "tour": "fields"})); r.log("clip-start", json!({"step": step, "visit": visit, "recordingId": rid, "window": "Adom Fields"}));@@ -5666,14 +5674,16 @@ fn main() { r.log("fields-emc", json!({"step": step, "visit": visit})); } std::thread::sleep(std::time::Duration::from_secs(2));+ let mut clip_file: Option<String> = None; if let Some(pth) = br.screenshot_window(win, &save_to, &format!("shot-{step}-{visit}-fields-end")) { r.log("artifact", json!({"step": step, "visit": visit, "kind": "screenshot", "when": "fields-end", "file": pth})); } let caps = r.data["captures"].as_array().cloned().unwrap_or_default(); if let Some(pos) = caps.iter().rposition(|c| c["recordingId"].as_str() == Some(&rid)) { let stop = br.record_stop(&rid, &save_to); let local = settle_clip_stop(&mut r, pos, &stop, &dir); r.log("clip-stop", clip_stop_event(json!({"step": step, "visit": visit, "recordingId": rid}), &stop));- if let Some(f) = local.filter(|f| Path::new(f).is_file()) { clip_artifacts(&mut r, &json!(format!("{step}-{visit}")), &f, 10); }+ if let Some(f) = local.filter(|f| Path::new(f).is_file()) { clip_artifacts(&mut r, &json!(format!("{step}-{visit}")), &f, 10); clip_file = Some(f); } }+ if raised { let _ = br.call("pup_lower_os_window", &json!({"sessionId": sid, "reason": "aiflow fields walkthrough take ended"})); } let _ = br.call("pup_close_window", &json!({"sessionId": sid})); let clips_note = auto_clips(&r, &dir); if !clips_note.is_empty() { eprintln!("{clips_note}"); }@@ -5682,12 +5692,16 @@ fn main() { let issues = fields["issues"].as_array().cloned().unwrap_or_default(); r.stage_end("tour-fields", Some(json!({"issues": issues.len(), "tour": tour_ok})), None); if tour_ok {- r.outcome("tour-fields", json!({"issues": issues, "nets": fields["nets"], "chips": fields["chips"], "heat": fields["heat"]}));+ r.outcome("tour-fields", json!({"issues": issues, "nets": fields["nets"], "chips": fields["chips"], "heat": fields["heat"], "clip": clip_file, "offscreen": offscreen, "raised": raised})); } r.log("artifact", json!({"step": step, "visit": visit, "kind": "fields-summary", "issues": issues.len(), "peakJ": fields["scales"]["jMax"], "peakRiseC": fields["heat"]["peakRiseC"]})); r.save().unwrap();+ // no silent pass (#123): the walkthrough ran, but there is no clip of it+ if clip_file.is_none() {+ eprintln!("WARNING: the fields walkthrough ran, but the recorder produced no clip of the app window{} (issue #123). The run's video will have no Fields segment: run `tour fields` again{}, or film the app another way and register it with `artifact`.", if offscreen { ", which was off-screen" } else { "" }, if offscreen && !raised { " once pup can raise its window (pup_raise_os_window)" } else { "" });+ } if !tour_ok { err("the app's walkthrough did not run (adom-fields tour failed)", &["`adom-fields tour` by hand shows why; the clip of this visit still exists.".into()]); }- ok(&format!("fields walkthrough recorded: {} issue(s) on the board, peak {:.0} A/mm², peak rise {:.0} °C in still air", issues.len(), fields["scales"]["jMax"].as_f64().unwrap_or(0.0), fields["heat"]["peakRiseC"].as_f64().unwrap_or(0.0)), &[if issues.is_empty() { "No issues: every loaded net under 60 A/mm² and every hot tab within its budget.".to_string() } else { format!("The issues are yours to answer before delivery: {}", issues.iter().take(4).map(|i| i["what"].as_str().unwrap_or("").to_string()).collect::<Vec<_>>().join(" | ")) }, "Next: step nets, or step 3d.".into(), on_note]);+ ok(&format!("fields walkthrough {}: {} issue(s) on the board, peak {:.0} A/mm², peak rise {:.0} °C in still air", if clip_file.is_some() { "recorded" } else { "ran, but NO CLIP was recorded (see the WARNING above)" }, issues.len(), fields["scales"]["jMax"].as_f64().unwrap_or(0.0), fields["heat"]["peakRiseC"].as_f64().unwrap_or(0.0)), &[if issues.is_empty() { "No issues: every loaded net under 60 A/mm² and every hot tab within its budget.".to_string() } else { format!("The issues are yours to answer before delivery: {}", issues.iter().take(4).map(|i| i["what"].as_str().unwrap_or("").to_string()).collect::<Vec<_>>().join(" | ")) }, "Next: step nets, or step 3d.".into(), on_note]); } other => err(&format!("tour {other}: use 3d, nets or fields"), &[]), }
package.json+1−1@@ -1,7 +1,7 @@ { "slug": "adom-aiflow", "type": "app",- "version": "0.2.38",+ "version": "0.2.39", "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.38",+ "version": "0.2.39", "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.",