From e06c152ad297563080c7394e45cb31a92dead370 Mon Sep 17 00:00:00 2001
From: Abdul Wahab
Date: Fri, 11 Sep 2026 00:07:15 +0500
Subject: [PATCH] generate lane: one-shot start, event in hand, mechanical bake
The generate command's fast lane spent most of its time on agent round
trips, not on the engine. Four engine changes take them out, all behind
the lane's own flags so a plain `live` session is untouched:
- `live-poll --reply done --then-poll` replies and waits for the
next event in one call; the reply's ack rides along as `_replyAck`.
- `live-generate` collects the session's own generate event into its
output (`GET /poll?types=generate&id=`, a new id filter
the parked-poll flush honours too), so the pickup poll is gone.
- `live-generate --boot` runs the lane's boot in-process and reuses a
running helper; `--dev-url ` names the dev server the agent
already knows and leads the probe; with no page connected the verdict
is `browser_needed` with the harness's own way to open the page
(Cursor browser_navigate, Claude Code's Browser pane, Codex --open or
the user). `--open` launches the system browser only on a harness
without one: on cursor and claude-code it is ignored unless
IMPECCABLE_BROWSER or the config's `browser` names a browser, so a
second window never opens beside the harness's. The served /live.js
carries the helper-wide bar preference in its prelude.
- The accept of a session the generate verb started (journaled with
origin "agent", or `--bake`) is baked mechanically: the accepted
variant's @scope rules are re-anchored on the element's own selector
and appended to the stylesheet that names it, the wrapper is
unwrapped, the source verified clean. Knobs, plumbing inside the
variant, no stylesheet, or a selector the rewrite cannot decide fall
back to the carbonize block with `bakeSkipped`. `--no-bake` refuses.
Goldens re-recorded for the three help texts and the no-browser case.
Written with AI assistance (Claude).
Co-Authored-By: Claude Fable 5
---
crates/cli/tests/agent_target.rs | 218 ++++++++
crates/live/src/bake.rs | 484 ++++++++++++++++++
crates/live/src/browser_assets.rs | 7 +
crates/live/src/browser_open.rs | 130 +++++
crates/live/src/instructions.rs | 16 +-
crates/live/src/lib.rs | 2 +
crates/live/src/live_accept.rs | 275 +++++++++-
crates/live/src/live_generate.rs | 458 ++++++++++++++++-
crates/live/src/live_poll.rs | 54 +-
crates/live/src/live_server.rs | 17 +-
crates/live/src/server_state.rs | 28 +-
crates/live/src/session.rs | 6 +
docs/CLI-CONTRACT.md | 18 +-
tests/oracle/golden/live-accept-help.json | 2 +-
.../golden/live-generate-local-verdicts.json | 2 +-
.../live-generate-no-browser-connected.json | 2 +-
tests/oracle/golden/live-poll-help.json | 2 +-
17 files changed, 1659 insertions(+), 62 deletions(-)
create mode 100644 crates/live/src/bake.rs
create mode 100644 crates/live/src/browser_open.rs
diff --git a/crates/cli/tests/agent_target.rs b/crates/cli/tests/agent_target.rs
index c59ae02c0..5a9cc7b43 100644
--- a/crates/cli/tests/agent_target.rs
+++ b/crates/cli/tests/agent_target.rs
@@ -687,3 +687,221 @@ fn agent_target_result_is_honored_only_from_the_lease_holder() {
assert_eq!(verdict["sessionId"], serde_json::json!("aabbccdd"), "{verdict}");
let _ = &mut b;
}
+
+/// The generate verb as the agent runs it, against this helper's dir.
+fn spawn_cli(s: &Server, args: &[&str]) -> std::process::Child {
+ std::process::Command::new(env!("CARGO_BIN_EXE_impeccable"))
+ .args(args)
+ .current_dir(&s.dir)
+ .env("IMPECCABLE_LIVE_COPY_AGENT", "off")
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()
+ .expect("spawn cli")
+}
+
+fn cli_json(child: std::process::Child) -> (i32, serde_json::Value, String) {
+ let out = child.wait_with_output().expect("cli output");
+ let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
+ let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
+ // live-generate prints one pretty object; live-poll prints one line.
+ let v: serde_json::Value = serde_json::from_str(stdout.trim())
+ .or_else(|_| serde_json::from_str(stdout.trim().lines().last().unwrap_or("")))
+ .unwrap_or_else(|e| panic!("{e}\nstdout: {stdout}\nstderr: {stderr}"));
+ (out.status.code().unwrap_or(-1), v, stderr)
+}
+
+#[test]
+fn live_generate_collects_its_own_generate_event_and_reply_then_poll_returns_the_next_one() {
+ let s = Server::start("collect");
+ let mut a = Overlay::connect(s.port, &s.token, "tab-a");
+ a.next(|m| m["type"] == "connected");
+ let child = spawn_cli(&s, &["live-generate", "--selector", "h1", "--action", "bolder", "--count", "3"]);
+ let target_id = a.next(|m| m["type"] == "agent_target")["targetId"].as_str().unwrap().to_string();
+ assert_eq!(s.claim(&target_id, "tab-a", true)["granted"], serde_json::json!(true));
+ let (status, ack) = post_json(s.port, "/events", generate_event_for(&s, &target_id, "c0ffee11", "tab-a"));
+ assert_eq!(status, 200, "{ack}");
+ let (code, verdict, stderr) = cli_json(child);
+ assert_eq!(code, 0, "{verdict}\n{stderr}");
+ assert_eq!(verdict["ok"], serde_json::json!(true), "{verdict}");
+ assert_eq!(verdict["sessionId"], serde_json::json!("c0ffee11"));
+ // B: the session's generate event rides along, leased, with the fast path.
+ assert_eq!(verdict["event"]["type"], serde_json::json!("generate"), "{verdict}");
+ assert_eq!(verdict["event"]["id"], serde_json::json!("c0ffee11"));
+ assert_eq!(verdict["event"]["origin"], serde_json::json!("agent"));
+ assert!(verdict["event"]["_instructions"].as_str().unwrap().contains("Fast path"), "{verdict}");
+ assert!(verdict["_instructions"].as_str().unwrap().contains("--reply c0ffee11 done --file --then-poll"), "{verdict}");
+ // Leased: a plain poll finds nothing else to hand out.
+ let (_, polled) = http(s.port, "GET", &format!("/poll?token={}&timeout=300", s.token), None);
+ assert!(polled.contains("\"timeout\""), "{polled}");
+ // A: reply done and wait for the next event in one call; a steer lands
+ // while it waits.
+ let child = spawn_cli(&s, &["live-poll", "--reply", "c0ffee11", "done", "--file", "index.html", "--then-poll", "--timeout=8000"]);
+ std::thread::sleep(Duration::from_millis(900));
+ let (status, body) = post_json(s.port, "/events", serde_json::json!({ "token": s.token, "type": "steer", "id": "c0ffee11", "message": "warmer" }));
+ assert_eq!(status, 200, "{body}");
+ let (code, event, stderr) = cli_json(child);
+ assert_eq!(code, 0, "{event}\n{stderr}");
+ assert_eq!(event["type"], serde_json::json!("steer"), "{event}");
+ assert_eq!(event["_replyAck"]["ok"], serde_json::json!(true), "{event}");
+ assert_eq!(event["_replyAck"]["status"], serde_json::json!("done"));
+ assert_eq!(event["_replyAck"]["file"], serde_json::json!("index.html"));
+ assert!(event["_replyAck"].get("_instructions").is_none(), "{event}");
+ assert!(event["_instructions"].as_str().unwrap().contains("steer_done"), "{event}");
+}
+
+#[test]
+fn live_generate_boot_and_open_run_the_lane_from_a_cold_project() {
+ // No helper running: --boot starts one (the lane's flags), --open hands the
+ // dev URL to the configured browser, and the overlay that page brings up
+ // serves the target.
+ let dir = std::env::temp_dir().join(format!("impeccable-agent-target-cold-{}", std::process::id()));
+ let _ = std::fs::remove_dir_all(&dir);
+ std::fs::create_dir_all(dir.join(".impeccable/live")).unwrap();
+ std::fs::write(dir.join("index.html"), "t
").unwrap();
+ std::fs::write(dir.join(".impeccable/live/config.json"), "{\"files\":[\"index.html\"],\"insertBefore\":\"
t