Files
pbakaus_impeccable/crates/live/src/session.rs
T
3bdb9ff06c Fix: drop stale carbonize diagnostic on complete (#801)
Complete and discarded snapshots no longer keep carbonize_cleanup_required after cleanup is done.

AI assistance: Cursor Grok 4.6.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 08:47:16 +05:00

1000 lines
36 KiB
Rust

//! JS: live/session-store.mjs. Append-only per-session journal
//! (`<id>.jsonl`) with a derived snapshot (`<id>.snapshot.json`), replayed
//! through the same reducer the server uses.
use crate::paths::{legacy_live_sessions_dir, live_sessions_dir, safe_session_id};
use crate::util::{
date_parse_ms, exists, iso_now, js_number, jsp, now_ms, read_dir_names_raw, safe_read, Env,
};
use serde_json::{json, Map, Value};
pub const COMPLETED_SESSION_PHASES: [&str; 2] = ["completed", "discarded"];
pub const GENERATION_FENCED_SESSION_PHASES: [&str; 5] = [
"accept_requested",
"discard_requested",
"carbonize_required",
"completed",
"discarded",
];
const META_JOURNAL_BYTES: &str = "__journalBytes";
const META_NEXT_SEQ: &str = "__nextSeq";
const MOUNT_FAILURE_HISTORY: usize = 5;
pub struct SessionStore {
pub root_dir: String,
pub legacy_root_dir: String,
pub session_id: Option<String>,
}
pub struct DerivedState {
pub snapshot: Map<String, Value>,
pub next_seq: i64,
pub journal_path: String,
pub size: i64,
}
fn journal_path(root: &str, id: &str) -> Result<String, String> {
Ok(jsp::join(&[
root,
&format!("{}.jsonl", safe_session_id(id)?),
]))
}
fn snapshot_path(root: &str, id: &str) -> Result<String, String> {
Ok(jsp::join(&[
root,
&format!("{}.snapshot.json", safe_session_id(id)?),
]))
}
fn file_size(p: &str) -> Option<i64> {
std::fs::metadata(p).ok().map(|m| m.len() as i64)
}
/// JS: createLiveSessionStore({ cwd, sessionId })
pub fn create_live_session_store(cwd: &str, env: &Env, session_id: Option<&str>) -> SessionStore {
let root_dir = live_sessions_dir(cwd, env);
let legacy_root_dir = legacy_live_sessions_dir(cwd, env);
let _ = std::fs::create_dir_all(&root_dir);
SessionStore {
root_dir,
legacy_root_dir,
session_id: session_id.map(String::from),
}
}
impl SessionStore {
fn readable_journal_path(&self, id: &str) -> Result<String, String> {
let primary = journal_path(&self.root_dir, id)?;
if exists(&primary) {
return Ok(primary);
}
let legacy = journal_path(&self.legacy_root_dir, id)?;
if exists(&legacy) {
return Ok(legacy);
}
Ok(primary)
}
/// JS: readState(id, { allowSnapshotFile })
pub fn read_state(&self, id: &str, allow_snapshot_file: bool) -> Result<DerivedState, String> {
let journal = self.readable_journal_path(id)?;
let size = file_size(&journal);
if allow_snapshot_file {
if let Some(sz) = size {
if let Some((snapshot, next_seq)) =
read_snapshot_file(&snapshot_path(&self.root_dir, id)?, id, sz)
{
return Ok(DerivedState {
snapshot,
next_seq,
journal_path: journal,
size: sz,
});
}
}
}
let (snapshot, next_seq) = rebuild_snapshot_from_journal(&journal, id);
Ok(DerivedState {
snapshot,
next_seq,
journal_path: journal,
size: size.unwrap_or(-1),
})
}
fn persist(
&self,
id: &str,
snapshot: &Map<String, Value>,
next_seq: i64,
) -> Result<(), String> {
let sp = snapshot_path(&self.root_dir, id)?;
let journal = self.readable_journal_path(id)?;
let size = file_size(&journal).unwrap_or(-1);
write_snapshot(&sp, snapshot, size, next_seq);
Ok(())
}
/// JS: appendEvent(event). Returns the new snapshot.
pub fn append_event(&self, event: &Value) -> Result<Map<String, Value>, String> {
let normalized = normalize_event(event, self.session_id.as_deref())?;
let id = normalized
.get("id")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let ty = normalized
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let jp = journal_path(&self.root_dir, &id)?;
let legacy = journal_path(&self.legacy_root_dir, &id)?;
if !exists(&jp) && exists(&legacy) {
let _ = std::fs::create_dir_all(jsp::dirname(&jp));
let _ = std::fs::copy(&legacy, &jp);
}
let prior = self.read_state(&id, true)?;
let entry = json!({
"seq": prior.next_seq,
"id": id,
"type": ty,
"ts": iso_now(),
"event": Value::Object(normalized.clone()),
});
{
use std::io::Write;
let _ = std::fs::create_dir_all(jsp::dirname(&jp));
let mut f = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&jp)
.map_err(|e| e.to_string())?;
let _ = f.write_all(
format!("{}\n", serde_json::to_string(&entry).unwrap_or_default()).as_bytes(),
);
}
let next = apply_event(&prior.snapshot, &entry);
self.persist(&id, &next, prior.next_seq + 1)?;
Ok(next)
}
/// JS: has(id)
pub fn has(&self, id: &str) -> bool {
if id.is_empty() {
return false;
}
match (
journal_path(&self.root_dir, id),
journal_path(&self.legacy_root_dir, id),
) {
(Ok(a), Ok(b)) => exists(&a) || exists(&b),
_ => false,
}
}
/// JS: getSnapshot(id, { includeCompleted }); None for terminal sessions
/// unless `include_completed`.
pub fn get_snapshot(
&self,
id: &str,
include_completed: bool,
) -> Result<Option<Map<String, Value>>, String> {
let state = self.read_state(id, true)?;
let phase = state
.snapshot
.get("phase")
.and_then(|p| p.as_str())
.unwrap_or("");
if !include_completed && COMPLETED_SESSION_PHASES.contains(&phase) {
return Ok(None);
}
Ok(Some(state.snapshot))
}
/// JS: flush(id)
pub fn flush(&self, id: &str) -> Result<Map<String, Value>, String> {
let state = self.read_state(id, false)?;
self.persist(id, &state.snapshot, state.next_seq)?;
Ok(state.snapshot)
}
/// JS: listActiveSessions(): all `*.jsonl` ids in legacy + primary,
/// sorted, non-terminal.
pub fn list_active_sessions(&self) -> Vec<Map<String, Value>> {
let mut ids: Vec<String> = Vec::new();
for dir in [&self.legacy_root_dir, &self.root_dir] {
if !exists(dir) {
continue;
}
for name in read_dir_names_raw(dir).unwrap_or_default() {
if let Some(id) = name.strip_suffix(".jsonl") {
if !ids.iter().any(|x| x == id) {
ids.push(id.to_string());
}
}
}
}
ids.sort();
ids.iter()
.filter_map(|id| self.get_snapshot(id, false).ok().flatten())
.collect()
}
}
fn read_snapshot_file(
path: &str,
id: &str,
journal_bytes: i64,
) -> Option<(Map<String, Value>, i64)> {
let text = safe_read(path)?;
let parsed: Value = serde_json::from_str(&text).ok()?;
let mut obj = parsed.as_object()?.clone();
if obj.get(META_JOURNAL_BYTES).and_then(|v| v.as_i64()) != Some(journal_bytes) {
return None;
}
let next_seq = obj.get(META_NEXT_SEQ).and_then(|v| v.as_i64())?;
obj.remove(META_JOURNAL_BYTES);
obj.remove(META_NEXT_SEQ);
if obj.get("id").and_then(|v| v.as_str()) != Some(id) {
return None;
}
let mut merged = base_snapshot(id);
for (k, v) in obj {
merged.insert(k, v);
}
Some((merged, next_seq))
}
fn normalize_event(event: &Value, fallback_id: Option<&str>) -> Result<Map<String, Value>, String> {
let Some(obj) = event.as_object() else {
return Err("event object required".to_string());
};
let id: Option<String> = match obj.get("id") {
Some(Value::String(s)) if !s.is_empty() => Some(s.clone()),
Some(v) if !matches!(v, Value::Null | Value::String(_)) && truthy(v) => {
// JS: `event.id || fallbackId` then `typeof id !== 'string'` throws
let _ = v;
return Err("event id required".to_string());
}
_ => fallback_id.map(String::from),
};
let Some(id) = id else {
return Err("event id required".to_string());
};
match obj.get("type") {
Some(Value::String(t)) if !t.is_empty() => {}
_ => return Err("event type required".to_string()),
}
let mut out = obj.clone();
out.insert("id".to_string(), Value::String(id));
Ok(out)
}
fn truthy(v: &Value) -> bool {
match v {
Value::Null => false,
Value::Bool(b) => *b,
Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
Value::String(s) => !s.is_empty(),
_ => true,
}
}
/// JS: baseSnapshot(id)
pub fn base_snapshot(id: &str) -> Map<String, Value> {
let v = json!({
"id": id,
"phase": "new",
"pageUrl": null,
"sourceFile": null,
"previewFile": null,
"previewMode": null,
"expectedVariants": 0,
"arrivedVariants": 0,
"visibleVariant": null,
"paramValues": {},
"pendingEventSeq": null,
"pendingEvent": null,
"deliveryLease": null,
"checkpointRevision": 0,
"browserCheckpointRevision": 0,
"publicationCheckpointRevision": 0,
"activeOwner": null,
"sourceMarkers": {},
"fallbackMode": null,
"generationPhase": null,
"generationCompletedAt": null,
"generationTimings": {},
"variantPlan": null,
"generationCanceled": false,
"generationCanceledAt": null,
"cancelReason": null,
"annotationArtifacts": [],
"mountedVariants": [],
"mountFailures": [],
"renderState": null,
"diagnostics": [],
"updatedAt": null,
});
v.as_object().cloned().unwrap_or_default()
}
fn derive_render_state(s: &Map<String, Value>) -> Value {
let mounted = s
.get("mountedVariants")
.and_then(|v| v.as_array())
.map(|a| a.len())
.unwrap_or(0);
let failures = s
.get("mountFailures")
.and_then(|v| v.as_array())
.map(|a| a.len())
.unwrap_or(0);
if mounted > 0 {
return json!("mounted");
}
if failures > 0 {
return json!("failed");
}
if truthy(s.get("generationCompletedAt").unwrap_or(&Value::Null)) {
return json!("pending");
}
Value::Null
}
/// JS: rebuildSnapshotFromJournal(journalPath, id) -> (snapshot, nextSeq)
pub fn rebuild_snapshot_from_journal(journal_path: &str, id: &str) -> (Map<String, Value>, i64) {
let mut snapshot = base_snapshot(id);
let mut diagnostics: Vec<Value> = Vec::new();
let mut next_seq: i64 = 1;
let Some(text) = safe_read(journal_path) else {
return (snapshot, next_seq);
};
for (i, line) in text.split('\n').enumerate() {
if impeccable_context::util::js_trim(line).is_empty() {
continue;
}
match serde_json::from_str::<Value>(line) {
Ok(entry) if entry.is_object() => {
if let Some(seq) = entry.get("seq").and_then(|s| s.as_i64()) {
next_seq = next_seq.max(seq + 1);
}
snapshot = apply_event(&snapshot, &entry);
}
Ok(_) => diagnostics.push(json!({ "error": "journal_parse_failed", "line": i + 1, "message": "entry is not object" })),
Err(e) => diagnostics.push(json!({ "error": "journal_parse_failed", "line": i + 1, "message": e.to_string() })),
}
}
if !diagnostics.is_empty() {
let mut d = snapshot
.get("diagnostics")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
d.extend(diagnostics);
snapshot.insert("diagnostics".to_string(), Value::Array(d));
}
(snapshot, next_seq)
}
fn is_nullish(v: Option<&Value>) -> bool {
matches!(v, None | Some(Value::Null))
}
/// `a ?? b`
fn coalesce<'a>(a: Option<&'a Value>, b: &'a Value) -> Value {
match a {
Some(v) if !v.is_null() => v.clone(),
_ => b.clone(),
}
}
fn get<'a>(m: &'a Map<String, Value>, k: &str) -> Option<&'a Value> {
m.get(k)
}
fn ts_ms(entry: &Value) -> Option<i64> {
// JS: Date.parse(entry.ts || '') || null
let ts = entry.get("ts").and_then(|t| t.as_str()).unwrap_or("");
date_parse_ms(ts).filter(|v| *v != 0)
}
fn to_pending_event(event: &Map<String, Value>) -> Value {
let mut p = event.clone();
p.shift_remove("token");
Value::Object(p)
}
fn push_diag(next: &mut Map<String, Value>, d: Value) {
let mut arr = next
.get("diagnostics")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
arr.push(d);
next.insert("diagnostics".to_string(), Value::Array(arr));
}
fn drop_diag(next: &mut Map<String, Value>, error: &str) {
let mut arr = next
.get("diagnostics")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
arr.retain(|d| d.get("error").and_then(|e| e.as_str()) != Some(error));
next.insert("diagnostics".to_string(), Value::Array(arr));
}
/// JS: applyEvent(snapshot, entry)
pub fn apply_event(snapshot: &Map<String, Value>, entry: &Value) -> Map<String, Value> {
let event: Map<String, Value> = match entry.get("event") {
Some(Value::Object(o)) => o.clone(),
Some(_) | None => entry.as_object().cloned().unwrap_or_default(),
};
let mut next = snapshot.clone();
// Spread copies (JS: fresh objects for the mutable containers).
for k in ["paramValues", "sourceMarkers", "generationTimings"] {
let v = next
.get(k)
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
next.insert(k.to_string(), Value::Object(v));
}
let vp = next.get("variantPlan").cloned().unwrap_or(Value::Null);
next.insert(
"variantPlan".to_string(),
if truthy(&vp) { vp } else { Value::Null },
);
for k in [
"annotationArtifacts",
"mountedVariants",
"mountFailures",
"diagnostics",
] {
let v = next
.get(k)
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
next.insert(k.to_string(), Value::Array(v));
}
let rs = next.get("renderState").cloned().unwrap_or(Value::Null);
next.insert("renderState".to_string(), rs);
next.insert(
"updatedAt".to_string(),
coalesce(
entry.get("ts").filter(|t| truthy(t)),
&Value::String(iso_now()),
),
);
let ev = |k: &str| event.get(k);
let evt_type = event
.get("type")
.and_then(|t| t.as_str())
.unwrap_or("")
.to_string();
let phase_now = next
.get("phase")
.and_then(|p| p.as_str())
.unwrap_or("")
.to_string();
let canceled = truthy(next.get("generationCanceled").unwrap_or(&Value::Null));
let fenced = GENERATION_FENCED_SESSION_PHASES.contains(&phase_now.as_str());
let seq = entry.get("seq").cloned();
let now_or_ts = |entry: &Value| -> Value {
match ts_ms(entry) {
Some(ms) => json!(ms),
None => json!(now_ms() as i64),
}
};
macro_rules! set_if {
($k:expr, $v:expr) => {{
let cur = next.get($k).cloned().unwrap_or(Value::Null);
next.insert($k.to_string(), coalesce($v, &cur));
}};
}
macro_rules! set {
($k:expr, $v:expr) => {
next.insert($k.to_string(), $v);
};
}
match evt_type.as_str() {
"generate" => {
set!("phase", json!("generate_requested"));
set_if!("pageUrl", ev("pageUrl"));
set_if!("expectedVariants", ev("count"));
set_if!("pendingEventSeq", seq.as_ref());
set!("pendingEvent", to_pending_event(&event));
set!("variantPlan", Value::Null);
set!("mountedVariants", json!([]));
set!("mountFailures", json!([]));
set!("renderState", Value::Null);
if let Some(sp) = ev("screenshotPath").filter(|v| truthy(v)) {
let mut arts = next
.get("annotationArtifacts")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
let exists_already = arts.iter().any(|a| {
a.get("path") == Some(sp)
&& a.get("type").and_then(|t| t.as_str()) == Some("screenshot")
});
if !exists_already {
arts.push(json!({ "type": "screenshot", "path": sp }));
}
set!("annotationArtifacts", Value::Array(arts));
}
}
"variant_plan" => {
if !canceled && !fenced {
set_if!("variantPlan", ev("plan"));
}
}
"detector_waivers" => {
if !canceled && !fenced {
let mut w = next
.get("detectorWaivers")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
if let Some(arr) = ev("waivers").and_then(|v| v.as_array()) {
w.extend(arr.iter().cloned());
}
set!("detectorWaivers", Value::Array(w));
}
}
"agent_phase" => {
set_if!("generationPhase", ev("phase"));
if let Some(phase) = ev("phase").filter(|v| truthy(v)) {
let key = match phase {
Value::String(s) => s.clone(),
other => other.to_string(),
};
let at = match ev("at") {
Some(v) if !v.is_null() => v.clone(),
_ => ts_ms(entry).map(|ms| json!(ms)).unwrap_or(Value::Null),
};
let dur = coalesce(ev("durationMs"), &Value::Null);
let mut timings = next
.get("generationTimings")
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
timings.insert(key, json!({ "at": at, "durationMs": dur }));
set!("generationTimings", Value::Object(timings));
}
}
"variants_ready" | "agent_done" => {
let carbonize = ev("carbonize") == Some(&Value::Bool(true));
if (canceled || fenced)
&& !(evt_type == "agent_done" && carbonize && phase_now == "accept_requested")
{
push_diag(
&mut next,
json!({ "error": "late_generation_event_ignored", "type": evt_type, "phase": phase_now }),
);
} else {
set!(
"phase",
json!(if carbonize {
"carbonize_required"
} else {
"variants_ready"
})
);
let at = match ev("at") {
Some(v) if !v.is_null() => v.clone(),
_ => now_or_ts(entry),
};
set!("generationCompletedAt", at);
let sf = coalesce(
ev("sourceFile"),
&coalesce(ev("file"), next.get("sourceFile").unwrap_or(&Value::Null)),
);
set!("sourceFile", sf);
set_if!("previewFile", ev("previewFile"));
set_if!("previewMode", ev("previewMode"));
let arrived = match ev("arrivedVariants") {
Some(v) if !v.is_null() => v.clone(),
_ => {
let expected = next.get("expectedVariants").cloned().unwrap_or(Value::Null);
if truthy(&expected) {
expected
} else {
let a = next.get("arrivedVariants").cloned().unwrap_or(Value::Null);
if truthy(&a) {
a
} else {
json!(0)
}
}
}
};
set!("arrivedVariants", arrived);
set!("pendingEventSeq", Value::Null);
set!("pendingEvent", Value::Null);
if carbonize {
let file = match ev("file") {
Some(v) if truthy(v) => v.clone(),
_ => Value::Null,
};
push_diag(
&mut next,
json!({
"error": "carbonize_cleanup_required",
"file": file,
"message": "Accepted variant still has carbonize markers that must be folded into source CSS.",
}),
);
}
let rs = derive_render_state(&next);
set!("renderState", rs);
}
}
"variant_mounted" => {
let variant = js_number(ev("variant"));
match variant {
Some(v) if v.fract() == 0.0 && v >= 1.0 => {
let v = v as i64;
let mut mounted: Vec<i64> = next
.get("mountedVariants")
.and_then(|m| m.as_array())
.map(|a| a.iter().filter_map(|x| x.as_i64()).collect())
.unwrap_or_default();
if !mounted.contains(&v) {
mounted.push(v);
mounted.sort();
}
set!("mountedVariants", json!(mounted));
let rs = derive_render_state(&next);
set!("renderState", rs);
}
_ => {
push_diag(
&mut next,
json!({ "error": "malformed_mount_ack", "type": evt_type, "variant": coalesce(ev("variant"), &Value::Null) }),
);
}
}
}
"variant_mount_failed" => {
let variant = js_number(ev("variant"));
match variant {
Some(v) if v.fract() == 0.0 && v >= 1.0 => {
let mut failures = next
.get("mountFailures")
.and_then(|m| m.as_array())
.cloned()
.unwrap_or_default();
let at = match ev("at") {
Some(a) if !a.is_null() => a.clone(),
_ => now_or_ts(entry),
};
failures.push(json!({
"variant": v as i64,
"url": ev("url").and_then(|u| u.as_str()).map(|s| json!(s)).unwrap_or(Value::Null),
"error": ev("error").and_then(|u| u.as_str()).map(|s| json!(s)).unwrap_or(Value::Null),
"at": at,
}));
if failures.len() > MOUNT_FAILURE_HISTORY {
failures = failures[failures.len() - MOUNT_FAILURE_HISTORY..].to_vec();
}
set!("mountFailures", Value::Array(failures));
let rs = derive_render_state(&next);
set!("renderState", rs);
if !truthy(next.get("pendingEvent").unwrap_or(&Value::Null)) {
set!("pendingEvent", to_pending_event(&event));
}
}
_ => {
push_diag(
&mut next,
json!({ "error": "malformed_mount_ack", "type": evt_type, "variant": coalesce(ev("variant"), &Value::Null) }),
);
}
}
}
"checkpoint" => {
if canceled || fenced {
push_diag(
&mut next,
json!({ "error": "checkpoint_after_terminal_ignored", "phase": coalesce(ev("phase"), &Value::Null), "revision": coalesce(ev("revision"), &Value::Null) }),
);
} else {
let publication = ev("revisionDomain").and_then(|v| v.as_str())
== Some("publication")
|| (ev("reason").and_then(|v| v.as_str()) == Some("variants_progress")
&& !truthy(ev("owner").unwrap_or(&Value::Null)));
let domain = if publication {
"publication"
} else {
"browser"
};
let field = if publication {
"publicationCheckpointRevision"
} else {
"browserCheckpointRevision"
};
let current_revision: f64 = match next.get(field) {
Some(v) if !v.is_null() => js_number(Some(v)).unwrap_or(f64::NAN),
_ => {
if domain == "browser" {
match next.get("checkpointRevision") {
Some(v) if !v.is_null() => js_number(Some(v)).unwrap_or(f64::NAN),
_ => 0.0,
}
} else {
0.0
}
}
};
let rev_val = coalesce(ev("revision"), &json!(0));
let rev_num = js_number(Some(&rev_val)).unwrap_or(f64::NAN);
if rev_num >= current_revision {
set_if!("phase", ev("phase"));
let cur = next.get(field).cloned().unwrap_or(Value::Null);
let stored = coalesce(
ev("revision"),
&if current_revision.is_nan() {
cur
} else {
crate::util::js_num(current_revision)
},
);
set!(field, stored);
if domain == "browser" {
set_if!("checkpointRevision", ev("revision"));
set_if!("activeOwner", ev("owner"));
}
set_if!("arrivedVariants", ev("arrivedVariants"));
if domain == "browser" {
set_if!("visibleVariant", ev("visibleVariant"));
}
set_if!("sourceFile", ev("sourceFile"));
set_if!("previewFile", ev("previewFile"));
set_if!("previewMode", ev("previewMode"));
if domain == "browser" {
if let Some(pv) = ev("paramValues").filter(|v| truthy(v)) {
let copy = pv
.as_object()
.cloned()
.map(Value::Object)
.unwrap_or_else(|| json!({}));
set!("paramValues", copy);
}
}
} else {
push_diag(
&mut next,
json!({ "error": "stale_checkpoint_ignored", "revision": ev("revision").cloned().unwrap_or(Value::Null), "revisionDomain": domain }),
);
// JS: `revision: event.revision` (undefined is dropped by JSON)
if is_nullish(ev("revision")) && ev("revision").is_none() {
if let Some(Value::Array(arr)) = next.get_mut("diagnostics") {
if let Some(Value::Object(last)) = arr.last_mut() {
last.shift_remove("revision");
}
}
}
}
}
}
"accept" | "accept_intent" => {
set!("phase", json!("accept_requested"));
set!("generationCanceled", json!(true));
let at = match ev("at") {
Some(a) if !a.is_null() => a.clone(),
_ => now_or_ts(entry),
};
set!("generationCanceledAt", at);
set!("cancelReason", json!("accept"));
let vid = coalesce(
ev("variantId"),
next.get("visibleVariant").unwrap_or(&Value::Null),
);
let n = js_number(Some(&vid));
set!(
"visibleVariant",
n.map(crate::util::js_num).unwrap_or(Value::Null)
);
if let Some(pv) = ev("paramValues").filter(|v| truthy(v)) {
let copy = pv
.as_object()
.cloned()
.map(Value::Object)
.unwrap_or_else(|| json!({}));
set!("paramValues", copy);
}
set_if!("pendingEventSeq", seq.as_ref());
set!("pendingEvent", to_pending_event(&event));
}
"manual_edit_apply" => {
set!("phase", json!("manual_edit_apply_requested"));
set_if!("pageUrl", ev("pageUrl"));
set_if!("pendingEventSeq", seq.as_ref());
set!("pendingEvent", to_pending_event(&event));
}
"steer" => {
set!("phase", json!("steer_requested"));
set_if!("pageUrl", ev("pageUrl"));
set_if!("pendingEventSeq", seq.as_ref());
set!("pendingEvent", to_pending_event(&event));
}
"carbonize_cleanup" => {
set!("phase", json!("carbonize_cleanup_requested"));
set_if!("sourceFile", ev("file"));
set_if!("pendingEventSeq", seq.as_ref());
set!("pendingEvent", to_pending_event(&event));
}
"steer_done" => {
set!("phase", json!("steer_done"));
let sf = coalesce(
ev("sourceFile"),
&coalesce(ev("file"), next.get("sourceFile").unwrap_or(&Value::Null)),
);
set!("sourceFile", sf);
set_if!("previewFile", ev("previewFile"));
set_if!("previewMode", ev("previewMode"));
// JS: next.message = event.message ?? next.message (key appears
// even when undefined... no: assigning undefined creates the key,
// JSON.stringify drops it).
match ev("message") {
Some(v) if !v.is_null() => {
set!("message", v.clone());
}
_ => {
if let Some(cur) = next.get("message").cloned() {
set!("message", cur);
}
}
}
set!("pendingEventSeq", Value::Null);
set!("pendingEvent", Value::Null);
}
"discard" => {
set!("phase", json!("discard_requested"));
set!("generationCanceled", json!(true));
let at = match ev("at") {
Some(a) if !a.is_null() => a.clone(),
_ => now_or_ts(entry),
};
set!("generationCanceledAt", at);
set!("cancelReason", json!("discard"));
set_if!("pendingEventSeq", seq.as_ref());
set!("pendingEvent", to_pending_event(&event));
}
"discarded" => {
set!("phase", json!("discarded"));
set!("pendingEventSeq", Value::Null);
set!("pendingEvent", Value::Null);
drop_diag(&mut next, "carbonize_cleanup_required");
}
"complete" => {
set!("phase", json!("completed"));
let sf = coalesce(
ev("sourceFile"),
&coalesce(ev("file"), next.get("sourceFile").unwrap_or(&Value::Null)),
);
set!("sourceFile", sf);
set_if!("previewFile", ev("previewFile"));
set_if!("previewMode", ev("previewMode"));
set!("pendingEventSeq", Value::Null);
set!("pendingEvent", Value::Null);
drop_diag(&mut next, "carbonize_cleanup_required");
}
"agent_error" => {
if canceled && ev("sourceEventType").and_then(|v| v.as_str()) == Some("generate") {
push_diag(
&mut next,
json!({ "error": "late_generation_event_ignored", "type": evt_type, "phase": phase_now }),
);
} else {
set!("phase", json!("agent_error"));
set!("pendingEventSeq", Value::Null);
set!("pendingEvent", Value::Null);
let msg = match ev("message") {
Some(v) if truthy(v) => v.clone(),
_ => json!("unknown agent error"),
};
push_diag(&mut next, json!({ "error": "agent_error", "message": msg }));
}
}
_ => {
push_diag(
&mut next,
json!({ "error": "unknown_event_type", "type": ev("type").cloned().unwrap_or(Value::Null) }),
);
if ev("type").is_none() {
if let Some(Value::Array(arr)) = next.get_mut("diagnostics") {
if let Some(Value::Object(last)) = arr.last_mut() {
last.shift_remove("type");
}
}
}
}
}
next
}
fn write_snapshot(path: &str, snapshot: &Map<String, Value>, journal_bytes: i64, next_seq: i64) {
let mut payload = snapshot.clone();
payload.insert(META_JOURNAL_BYTES.to_string(), json!(journal_bytes));
payload.insert(META_NEXT_SEQ.to_string(), json!(next_seq));
let _ = crate::util::write_file(
path,
&format!("{}\n", crate::util::json_pretty(&Value::Object(payload))),
);
}
/// The pending event's `id`/`type` as strings (helper for status/resume).
pub fn get_str<'a>(m: &'a Map<String, Value>, k: &str) -> Option<&'a str> {
get(m, k).and_then(|v| v.as_str())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn journal_entry(seq: i64, event: Value) -> Value {
json!({ "seq": seq, "ts": "2026-01-01T00:00:00.000Z", "event": event })
}
fn has_diag(snapshot: &Map<String, Value>, error: &str) -> bool {
snapshot
.get("diagnostics")
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.any(|d| d.get("error").and_then(|e| e.as_str()) == Some(error))
})
.unwrap_or(false)
}
fn replay(id: &str, events: &[Value]) -> Map<String, Value> {
let mut snap = base_snapshot(id);
for entry in events {
snap = apply_event(&snap, entry);
}
snap
}
fn accept_carbonize_done(id: &str, terminal: &str) -> Map<String, Value> {
replay(
id,
&[
journal_entry(
1,
json!({ "id": id, "type": "accept", "variantId": 2 }),
),
journal_entry(
2,
json!({ "id": id, "type": "agent_done", "carbonize": true, "file": "index.html" }),
),
journal_entry(3, json!({ "id": id, "type": terminal })),
],
)
}
#[test]
fn complete_drops_carbonize_cleanup_required() {
let snap = accept_carbonize_done("ab12cd34", "complete");
assert_eq!(snap.get("phase").and_then(|p| p.as_str()), Some("completed"));
assert!(!has_diag(&snap, "carbonize_cleanup_required"));
}
#[test]
fn discarded_drops_carbonize_cleanup_required() {
let snap = accept_carbonize_done("ab12cd34", "discarded");
assert_eq!(snap.get("phase").and_then(|p| p.as_str()), Some("discarded"));
assert!(!has_diag(&snap, "carbonize_cleanup_required"));
}
}