docs(comments): rewrite comments across all crates to the guidelines

Sweep every first-party crate source (1956 .rs files) to the project comment
guidelines: delete redundant restatements, decorative banners, change
narration, and end-of-line comments; keep and tighten the crucial ones
(invariants, bug rationale, SAFETY blocks, ported-source attribution).

No functional code changed. Every edit is proven comment-only against the
prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a
separate doctest-fence check. Where removing a comment made rustfmt or clippy
want to re-lay-out adjacent code, the minimal triggering comment is restored so
code tokens stay byte-identical.

Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy
--workspace --all-targets (0 warnings).

Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for
these guidelines (flags banners, end-of-line comments, change narration, and
commented-out code).
This commit is contained in:
2026-07-23 16:55:39 -04:00
parent ff0fb56c67
commit a02b555e66
1458 changed files with 10729 additions and 21750 deletions
@@ -6,8 +6,7 @@
/// background `monitor`/bash task (`TaskBackgrounded`) must restore into
/// `bg_tasks` on a resumed / second terminal — not be dropped by the
/// default match arm — so the idle "watching" status line and the Tasks pane
/// match the originating terminal. (Before this routing only subagents
/// survived resume.)
/// match the originating terminal.
#[test]
fn ext_session_update_replay_restores_bg_task() {
let mut app = make_app_with_agent("sess-1");
@@ -129,7 +128,6 @@
setup_pending_execute_tool(&mut app, tc_id);
send_late_bg_detection(&mut app, tc_id);
// Tool should be in BOTH pending_tools and bg_deferred_tools
let agent = app.agents.get(&AgentId(0)).unwrap();
assert!(agent.session.tracker.pending_tool_entry_id(tc_id).is_some());
assert!(agent.session.tracker.bg_deferred_tools.contains_key(tc_id));
@@ -260,14 +258,12 @@
assert!(changed);
let agent = app.agents.get(&AgentId(0)).unwrap();
// Parent scrollback must NOT have the bg task block.
assert_eq!(agent.scrollback.len(), 0, "parent scrollback must be empty");
assert!(
agent.session.bg_tasks.is_empty(),
"parent session must not have the bg task"
);
// Child view must have the bg task.
let child = agent.subagent_views.get("child-sess").unwrap();
assert_eq!(child.scrollback.len(), 1);
assert!(child.session.bg_tasks.contains_key("task-child-1"));
@@ -403,25 +399,21 @@
fn task_completed_routes_to_child_session() {
let mut app = make_app_with_parent_and_child("parent-sess", "child-sess");
// First, background a task on the child.
let bg_notif =
make_task_backgrounded_notif("child-sess", "tc-child-2", "task-child-2", "echo hi");
handle_task_backgrounded(&bg_notif, &mut app);
// Now complete it.
let notif = make_task_completed_notif("child-sess", "task-child-2", "echo hi", Some(0));
let changed = handle_task_completed(&notif, &mut app);
assert!(changed);
let agent = app.agents.get(&AgentId(0)).unwrap();
// Parent must NOT have a completion block.
assert_eq!(
agent.scrollback.len(),
0,
"parent scrollback must not have completion block"
);
// Child must have both the started and completed blocks.
let child = agent.subagent_views.get("child-sess").unwrap();
assert_eq!(child.scrollback.len(), 2, "child: started + completed");
let bg = child.session.bg_tasks.get("task-child-2").unwrap();
@@ -432,7 +424,6 @@
fn task_completed_root_still_routes_to_parent() {
let mut app = make_app_with_parent_and_child("parent-sess", "child-sess");
// Background and complete a task on the parent.
let bg_notif =
make_task_backgrounded_notif("parent-sess", "tc-root-2", "task-root-2", "echo root");
handle_task_backgrounded(&bg_notif, &mut app);
@@ -510,7 +501,6 @@
fn monitor_event_root_still_routes_to_parent() {
let mut app = make_app_with_parent_and_child("parent-sess", "child-sess");
// Background a task on the parent.
let bg_notif =
make_task_backgrounded_notif("parent-sess", "tc-root-3", "task-root-3", "tail -f");
handle_task_backgrounded(&bg_notif, &mut app);
@@ -560,7 +550,6 @@
fn task_completed_child_inactive_returns_false_but_mutates_state() {
let mut app = make_app_with_parent_and_child("parent-sess", "child-sess");
// Background a task on the child first.
let bg_notif = make_task_backgrounded_notif(
"child-sess",
"tc-compl-inact",
@@ -569,7 +558,6 @@
);
handle_task_backgrounded(&bg_notif, &mut app);
// Now switch away.
let other = make_agent(Some("other-sess"));
app.agents.insert(AgentId(1), other);
crate::app::dispatch::switch_to_agent(
@@ -30,7 +30,6 @@
.session
.current_prompt_id = Some("p1".into());
// Active turn (p1) chips applied via the wire.
assert!(handle_ext_notification(
&follow_ups_ext_with_prompt("resp-1", "p1", &["a"]),
&mut app
@@ -39,7 +38,6 @@
app.agents.get_mut(&AgentId(0)).unwrap().clear_follow_ups();
assert!(app.agents[&AgentId(0)].follow_ups.is_none());
// (a) Re-delivery of the active turn re-renders.
assert!(
handle_ext_notification(
&follow_ups_ext_with_prompt("resp-1", "p1", &["a"]),
@@ -56,7 +54,6 @@
"resp-1"
);
// Adopt a new turn p2; clear.
app.agents
.get_mut(&AgentId(0))
.unwrap()
@@ -64,7 +61,6 @@
.current_prompt_id = Some("p2".into());
app.agents.get_mut(&AgentId(0)).unwrap().clear_follow_ups();
// (b) Prior turn (p1) replay must NOT revive.
assert!(
!handle_ext_notification(
&follow_ups_ext_with_prompt("resp-1", "p1", &["a"]),
@@ -199,7 +195,6 @@
let affected = handle_ext_notification(&follow_ups_ext(&big, &["x"]), &mut app);
assert!(!affected, "an oversized response_id must be rejected");
assert!(app.agents[&AgentId(0)].follow_ups.is_none());
// A sane-length id still works.
let ok = "r".repeat(super::MAX_RESPONSE_ID_LEN);
assert!(handle_ext_notification(
&follow_ups_ext(&ok, &["x"]),
@@ -1,8 +1,6 @@
#![cfg_attr(rustfmt, rustfmt::skip)]
use super::*;
// ── derive_child_cwd ─────────────────────────────────────────────
#[test]
fn derive_child_cwd_uses_child_cwd_from_info() {
let parent_cwd = PathBuf::from("/parent/cwd");
@@ -89,7 +87,6 @@
);
assert!(child_view.is_worktree);
assert_eq!(child_view.main_repo.as_deref(), Some("main-repo"));
// Parent must not be affected.
assert!(parent.current_branch.is_none());
assert!(!parent.is_worktree);
}
@@ -45,11 +45,6 @@
"last_event": "verify_started",
"last_event_detail": "round 2 of 3",
"last_event_timestamp": "2026-05-24T00:00:00Z",
// Field absent on today's `SessionUpdate::GoalUpdated` — simulates
// a future shell adding a new wire field. With trailing `..` in
// the destructure and no `deny_unknown_fields` on the variant,
// this must parse and the pager must still produce a
// GoalDisplayState mapped from the known subset.
"future_field_for_pr5": "ignored-by-todays-pager"
}
});
@@ -107,7 +102,6 @@
Some("2026-05-24T00:00:00Z")
);
assert_eq!(goal.pause_message, None);
// Classifier fields default to `None` / `false` when absent.
assert_eq!(goal.classifier_runs_attempted, None);
assert_eq!(goal.classifier_max_runs, None);
assert_eq!(goal.last_classifier_verdict, None);
@@ -177,8 +171,6 @@
"transition to Complete pushes one e2e marker with the goal's total time",
);
// A repeat Complete update (e.g. a late notification) must not
// duplicate the marker.
send(&mut app, "complete", 620_000);
assert_eq!(
goal_markers(&app).len(),
@@ -238,7 +230,6 @@
"chip cleared on cleared status"
);
// A stale late update for the cleared goal must not resurrect it.
let affected = send_goal_update(&mut app, "g1", "complete", 5_000);
assert!(
app.agents.get(&AgentId(0)).unwrap().goal_state.is_none(),
@@ -268,7 +259,6 @@
// the prior goal's carried elapsed floor.
let mut app = make_app_with_agent("sess-A");
send_goal_update(&mut app, "g1", "active", 10_000);
// Switch directly to a different goal with a small elapsed base.
send_goal_update(&mut app, "g2", "active", 500);
let elapsed = app
.agents
@@ -290,7 +280,6 @@
// on receipt into the cached bool (no per-frame stat).
let mut app = make_app_with_agent("sess-A");
// A real on-disk path → cached exists = true.
let f = tempfile::NamedTempFile::new().unwrap();
let real_path = f.path().to_string_lossy().into_owned();
let mut update = goal_update_value("g1", "active", 0);
@@ -312,7 +301,6 @@
Some(real_path.as_str())
);
// A missing path → cached exists = false (modal renders "(unavailable)").
let mut update = goal_update_value("g1", "active", 0);
update["last_classifier_details_path"] = serde_json::json!("/no/such/details-xyz.md");
dispatch_goal_update(&mut app, update);
@@ -336,9 +324,7 @@
// be omitted from the wire payload and must surface as `None` in
// the destructured arm — i.e. the pager keeps mapping the known
// subset cleanly when the shell-side struct grows or when an
// older shell omits newer optional fields. Drop a handful of
// optional keys from the payload and assert they materialise as
// `None` on the resulting `GoalDisplayState`.
// older shell omits newer optional fields.
let mut app = make_app_with_agent("sess-A");
let raw_payload = serde_json::json!({
@@ -349,26 +335,14 @@
"objective": "minimal payload",
"status": "active",
"phase": "idle",
// token_budget omitted — Option<i64> must default to None.
"tokens_used": 0,
"elapsed_ms": 0,
"total_deliverables": 0,
"completed_deliverables": 0,
// current_deliverable_idx omitted — Option<u32> -> None.
// current_deliverable_title omitted — Option<String> -> None.
// current_subagent_role omitted — Option<String> -> None.
"total_worker_rounds": 0,
"total_verify_rounds": 0,
"token_baseline": 0,
"finished_subagent_tokens": 0,
// live_subagent_tokens omitted — Option<u64> -> None.
// live_context_pct omitted — Option<u8> -> None.
// live_turn_count omitted — Option<u32> -> None.
// live_tool_call_count omitted — Option<u32> -> None.
// last_event omitted — Option<String> -> None.
// last_event_detail omitted — Option<String> -> None.
// last_event_timestamp omitted — Option<String> -> None.
// pause_message omitted — Option<String> -> None.
}
});
let raw = serde_json::value::to_raw_value(&raw_payload).unwrap();
@@ -391,7 +365,6 @@
.as_ref()
.expect("GoalUpdated must populate goal_state even with all Option fields omitted");
// Required fields landed as sent.
assert_eq!(goal.goal_id, "g-min");
assert_eq!(goal.objective, "minimal payload");
assert_eq!(goal.status, GoalDisplayStatus::Active);
@@ -405,9 +378,6 @@
assert_eq!(goal.token_baseline, 0);
assert_eq!(goal.finished_subagent_tokens, 0);
// Every omitted Option<T> wire field must surface as None — this
// is the property that keeps the destructure stable as the shell
// grows additive optional fields.
assert_eq!(goal.token_budget, None, "token_budget");
assert_eq!(goal.current_deliverable_id, None, "current_deliverable_id");
assert_eq!(
@@ -75,9 +75,8 @@
#[test]
fn permission_for_inactive_agent_queues_on_owning_agent() {
// The headline behavior change in handle_permission_request:
// permissions for an inactive owning agent now QUEUE (not cancel)
// so the user sees them on switching back.
// A permission for an inactive owning agent queues on that agent (rather
// than being cancelled), so the user sees it on switching back.
let mut app = make_app_with_agent("sess-A");
insert_agent(&mut app, AgentId(1), Some("sess-B"));
switch_active_to(&mut app, AgentId(1));
@@ -101,8 +100,6 @@
!affected,
"permission queued on a non-active agent must not request a redraw"
);
// Permission is still pending; the response_tx must still be alive
// (no auto-cancel was sent).
assert!(
rx.try_recv().is_err(),
"permission must NOT have been answered yet (queued, not cancelled)"
@@ -255,8 +252,6 @@
);
}
// ── Plan approval persistence tests ─────────────────────────
#[test]
fn close_viewer_preserves_plan_approval_state() {
let mut app = make_app_with_agent("sess-A");
@@ -279,11 +274,9 @@
let agent = app.agents.get(&AgentId(0)).unwrap();
assert!(agent.plan_approval_view.is_some(), "approval should be set");
// Close the viewer (simulates Esc / close button).
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
agent.cancel_line_viewer();
// Approval state must survive the close.
let agent = app.agents.get(&AgentId(0)).unwrap();
assert!(
agent.plan_approval_view.is_some(),
@@ -291,7 +284,6 @@
);
assert!(agent.line_viewer.is_none(), "viewer should be closed");
// Response must NOT have been sent (still waiting for user).
assert!(
rx.try_recv().is_err(),
"response must not be sent on viewer close"
@@ -323,12 +315,10 @@
&mut app,
);
// Close viewer.
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
agent.cancel_line_viewer();
assert!(agent.line_viewer.is_none());
// Reopen plan preview — inline content is in plan_approval_view.plan_content.
agent.show_plan_preview();
assert!(agent.line_viewer.is_some(), "viewer should reopen");
@@ -361,11 +351,9 @@
&mut app,
);
// Close viewer.
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
agent.cancel_line_viewer();
// User types new text in the prompt while viewer is closed.
agent.prompt.set_text("my new prompt text");
agent.reopen_plan_approval();
@@ -378,7 +366,6 @@
"stashed prompt should be restored after reopen + approve"
);
// Response should be approved.
let response = rx.blocking_recv().expect("should have sent response");
let raw = response.expect("should be Ok");
let parsed: serde_json::Value = serde_json::from_str(raw.0.get()).unwrap();
@@ -64,7 +64,6 @@
assert_eq!(count_parked(agent), 0, "no marker on screen");
}
// A task completing in the still-parked window must stay silent.
handle_ext_notification(
&make_task_completed_notif("sess-park", "t10", "sleep 10", Some(0)),
&mut app,
@@ -101,7 +100,6 @@
assert!(agent.renders_parked());
}
// sleep 10 exits → full marker with "2 commands still running."
handle_ext_notification(
&make_task_completed_notif("sess-park", "t10", "sleep 10", Some(0)),
&mut app,
@@ -111,12 +109,10 @@
&make_task_completed_notif("sess-park", "t10", "sleep 10", Some(0)),
&mut app,
);
// sleep 15 exits → full marker with "1 command still running."
handle_ext_notification(
&make_task_completed_notif("sess-park", "t15", "sleep 15", Some(0)),
&mut app,
);
// sleep 20 exits → nothing left; no "0 commands" line.
handle_ext_notification(
&make_task_completed_notif("sess-park", "t20", "sleep 20", Some(0)),
&mut app,
@@ -438,7 +434,6 @@
),
&mut app,
);
// Only the initial parked marker — no countdown re-push.
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
assert_eq!(parked_marker_messages(agent).len(), 1);
}
@@ -465,8 +460,6 @@
assert!(parked_marker_messages(agent).is_empty());
}
// -- imminent waits do not park (awaited work already finished) ----------
/// Waiting on a task that already completed: no marker, slot stays free.
#[test]
fn wait_on_already_completed_task_pushes_no_parked_marker() {
@@ -715,9 +708,9 @@
#[test]
fn interjection_notification_pushes_block_to_matching_session() {
// Multi-client fix: an interjection typed in one pane is broadcast by
// the shell as kigi/session/interjection; EVERY attached pane (incl.
// the originator, which no longer pushes a local block) renders it.
// An interjection typed in one pane is broadcast by the shell as
// kigi/session/interjection; every attached pane including the
// originator — renders it from the broadcast rather than a local push.
let mut app = make_app_with_agent("sess-view");
let affected =
handle_ext_notification(&interjection_ext("sess-view", "also add tests"), &mut app);
@@ -746,8 +739,6 @@
#[test]
fn interjection_notification_renders_for_a_viewer() {
// A viewer (attached_as_viewer) watching another client's session must
// also render interjections broadcast for that session.
let mut app = make_app_with_agent("sess-view");
app.agents.get_mut(&AgentId(0)).unwrap().attached_as_viewer = true;
let affected =
@@ -3,9 +3,6 @@
#[test]
fn mcp_init_progress_updates_seeded_progress_in_place() {
// When a session is seeded with mcp_init_progress{0,0}, a
// subsequent init_progress notification must update total and
// connected IN PLACE — preserving started_at for timer accuracy.
let mut app = make_app_with_agent("sess-1");
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
agent.mcp_init_progress = Some(crate::app::agent_view::McpInitProgress {
@@ -66,7 +63,6 @@
"owner is background — mutation must not request a redraw"
);
// Owner mutated.
let owner_modal = app
.agents
.get(&AgentId(0))
@@ -81,7 +77,6 @@
assert_eq!(owner_servers[0].tool_count, 2);
assert_eq!(owner_servers[0].tools.len(), 2);
// Active agent's modal must be untouched.
let active_modal = app
.agents
.get(&AgentId(1))
@@ -101,8 +96,6 @@
#[test]
fn mcp_init_progress_creates_when_none() {
// When mcp_init_progress is None (no seed), init_progress
// creates a fresh McpInitProgress.
let mut app = make_app_with_agent("sess-1");
assert!(app.agents[&AgentId(0)].mcp_init_progress.is_none());
@@ -117,7 +110,6 @@
#[test]
fn mcp_initialized_clears_progress() {
// kigi/mcp_initialized must set mcp_init_progress to None.
let mut app = make_app_with_agent("sess-1");
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
agent.mcp_init_progress = Some(crate::app::agent_view::McpInitProgress {
@@ -137,9 +129,6 @@
#[test]
fn mcp_full_lifecycle_seed_to_clear() {
// Full N-server lifecycle:
// seed(0/0) → init_progress(0/3) → init_progress(2/3)
// → init_progress(3/3) → mcp_initialized → None
let mut app = make_app_with_agent("sess-1");
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
agent.mcp_init_progress = Some(crate::app::agent_view::McpInitProgress {
@@ -148,22 +137,18 @@
started_at: Instant::now(),
});
// Shell reports real count.
handle_ext_notification(&make_mcp_init_progress_notif(3, 0), &mut app);
let p = app.agents[&AgentId(0)].mcp_init_progress.as_ref().unwrap();
assert_eq!((p.total, p.connected), (3, 0));
// Incremental progress.
handle_ext_notification(&make_mcp_init_progress_notif(3, 2), &mut app);
let p = app.agents[&AgentId(0)].mcp_init_progress.as_ref().unwrap();
assert_eq!((p.total, p.connected), (3, 2));
// All connected.
handle_ext_notification(&make_mcp_init_progress_notif(3, 3), &mut app);
let p = app.agents[&AgentId(0)].mcp_init_progress.as_ref().unwrap();
assert_eq!((p.total, p.connected), (3, 3));
// mcp_initialized clears everything.
handle_ext_notification(&make_mcp_initialized_notif("sess-1"), &mut app);
assert!(
app.agents[&AgentId(0)].mcp_init_progress.is_none(),
@@ -173,10 +158,10 @@
#[test]
fn mcp_zero_server_lifecycle() {
// 0-server lifecycle (the bug scenario):
// 0-server bug scenario: for 0 servers the shell must still emit
// mcp_initialized; without that terminal event the progress
// indicator sticks forever.
// seed(0/0) → init_progress(0/0) → mcp_initialized → None
// Previously mcp_initialized was never sent for 0 servers,
// leaving a stuck progress indicator.
let mut app = make_app_with_agent("sess-1");
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
agent.mcp_init_progress = Some(crate::app::agent_view::McpInitProgress {
@@ -185,12 +170,10 @@
started_at: Instant::now(),
});
// Shell sends 0/0 for the 0-server case.
handle_ext_notification(&make_mcp_init_progress_notif(0, 0), &mut app);
let p = app.agents[&AgentId(0)].mcp_init_progress.as_ref().unwrap();
assert_eq!((p.total, p.connected), (0, 0));
// Shell now sends mcp_initialized (root-cause fix).
handle_ext_notification(&make_mcp_initialized_notif("sess-1"), &mut app);
assert!(
app.agents[&AgentId(0)].mcp_init_progress.is_none(),
@@ -200,9 +183,6 @@
#[test]
fn mcp_init_progress_routes_to_background_session() {
// init_progress carrying a background session's sessionId must update
// *that* agent's indicator, not the foregrounded one, and must not
// force a redraw (the background spinner isn't visible).
let mut app = make_app_with_agent("sess-A");
app.agents.insert(AgentId(1), make_agent(Some("sess-B")));
@@ -223,10 +203,9 @@
#[test]
fn mcp_initialized_routes_to_background_session() {
// mcp_initialized for a background session must clear *that* agent's
// indicator while leaving the foreground agent's intact. Previously
// the clear was applied to whichever agent was active, so a
// background agent's spinner could stick forever.
// mcp_initialized must clear the indicator of the agent named by
// sessionId, not whichever agent is active — else a background
// agent's spinner sticks forever.
let mut app = make_app_with_agent("sess-A");
app.agents.insert(AgentId(1), make_agent(Some("sess-B")));
for id in [AgentId(0), AgentId(1)] {
@@ -256,8 +235,6 @@
#[test]
fn mcp_initialized_unknown_session_is_dropped() {
// An mcp_initialized for a session that matches no agent must not
// clear anyone's indicator (no misrouting to the active agent).
let mut app = make_app_with_agent("sess-A");
app.agents.get_mut(&AgentId(0)).unwrap().mcp_init_progress =
Some(crate::app::agent_view::McpInitProgress {
@@ -289,7 +266,6 @@
connected: 1,
started_at: Instant::now(),
});
// Register a subagent child view keyed by the child session id.
app.agents
.get_mut(&AgentId(0))
.unwrap()
@@ -299,7 +275,6 @@
Box::new(make_agent(Some("child-sess"))),
);
// init_progress for the child session must leave the parent untouched.
let changed = handle_ext_notification(
&make_mcp_init_progress_notif_for(5, 0, "child-sess"),
&mut app,
@@ -315,7 +290,6 @@
"parent spinner must be untouched by a subagent's init",
);
// mcp_initialized for the child session must not clear the parent.
let changed =
handle_ext_notification(&make_mcp_initialized_notif_for("child-sess"), &mut app);
assert!(!changed);
@@ -329,8 +303,6 @@
fn server_status_handler_noop_when_modal_closed_background() {
use kigi_shell::extensions::mcp::McpServerStatus;
let mut app = make_app_two_agents();
// Owner is background and has NO modal open. server_status
// must be a silent no-op (no Effect scheduling, no redraw).
let notif = make_server_status_notif("sess-owner", "alpha", McpServerStatus::Ready, None);
let redraw = handle_mcp_server_status(&notif, &mut app);
assert!(!redraw, "closed-modal cheap path must not request a redraw");
@@ -355,8 +327,6 @@
fn server_status_handler_noop_when_modal_closed_foreground() {
use kigi_shell::extensions::mcp::McpServerStatus;
let mut app = make_app_two_agents();
// Foreground = agent 1 (sess-active). Send a push targeting
// the foregrounded agent, no modal open.
let notif = make_server_status_notif("sess-active", "alpha", McpServerStatus::Ready, None);
let redraw = handle_mcp_server_status(&notif, &mut app);
assert!(
@@ -504,7 +474,6 @@
#[test]
fn servers_updated_broadcasts_to_every_agent_with_open_modal() {
let mut app = make_app_two_agents();
// Open modals on BOTH agents — broadcast must hit both.
seed_owner_agent_with_open_modal(&mut app);
{
let active = app.agents.get_mut(&AgentId(1)).unwrap();
@@ -552,13 +521,9 @@
assert_eq!(targets, vec![0, 1]);
}
/// Agents without an open modal must NOT receive a refetch (cheap
/// path) even though they are eligible to receive the broadcast in
/// principle.
#[test]
fn servers_updated_skips_agents_with_closed_modal() {
let mut app = make_app_two_agents();
// Only agent 1 (foregrounded) has a modal.
{
let active = app.agents.get_mut(&AgentId(1)).unwrap();
active.extensions_modal = Some(make_mcps_modal_with_servers(Vec::new()));
@@ -623,9 +588,6 @@
fn mcp_initialized_clears_init_progress_on_owner() {
use crate::app::agent_view::McpInitProgress;
let mut app = make_app_two_agents();
// Seed init progress on the OWNER (agent 0) and on the
// active view (agent 1). The push must clear only the
// owner's overlay.
{
let owner = app.agents.get_mut(&AgentId(0)).unwrap();
owner.mcp_init_progress = Some(McpInitProgress {
@@ -698,7 +660,6 @@
#[test]
fn tools_changed_pre_h2_falls_back_to_active_view() {
let mut app = make_app_two_agents();
// Active agent (agent 1) gets a modal; owner (agent 0) does not.
{
let active = app.agents.get_mut(&AgentId(1)).unwrap();
active.extensions_modal = Some(make_mcps_modal_with_servers(Vec::new()));
@@ -122,7 +122,6 @@ pub(super) fn compressed_entry(
compressed_height: 1018,
}
}
/// Most recent `SessionEvent` pushed to the scrollback, if any.
pub(super) fn last_session_event(sb: &ScrollbackState) -> Option<SessionEvent> {
(0..sb.len())
.rev()
@@ -160,7 +159,6 @@ pub(super) fn interjection_broadcast(
),
)
}
/// A Running background task registered on the agent's root session.
pub(super) fn insert_running_task(agent: &mut AgentView, task_id: &str, command: &str) {
agent
.session
@@ -363,7 +361,6 @@ pub(super) fn queue_changed_ext(session_id: &str, ids: &[&str]) -> acp::ExtNotif
std::sync::Arc::from(serde_json::value::to_raw_value(&params).unwrap()),
)
}
/// Build a `kigi/queue/changed` notification carrying `runningPromptId`.
pub(super) fn queue_changed_running(
session_id: &str,
ids: &[&str],
@@ -407,7 +404,6 @@ pub(super) fn app_with_running_p1_and_stashed_b1() -> AppView {
assert!(app.pending_running_adoptions.contains_key(& AgentId(0)));
app
}
/// Drive a live Execute tool_call `session/update` through the full handler.
pub(super) fn send_tool_call_update(
app: &mut AppView,
prompt_id: &str,
@@ -438,7 +434,6 @@ pub(super) fn send_tool_call_update(
app,
);
}
/// Dispatch an `Ok(EndTurn)` PromptResponse for `prompt_id`.
pub(super) fn prompt_response(app: &mut AppView, prompt_id: &str) {
use crate::app::actions::{Action, TaskResult};
crate::app::dispatch::dispatch(
@@ -567,7 +562,6 @@ pub(super) fn make_token_notification_message(
})
}
use crate::scrollback::block::RenderBlock;
/// Build an `AgentMessageChunk` notification carrying `text` for `session_id`.
pub(super) fn make_agent_chunk_message(
session_id: &str,
text: &str,
@@ -584,7 +578,6 @@ pub(super) fn make_agent_chunk_message(
response_tx: tx,
})
}
/// `AgentMessageChunk` with `promptId`/`isReplay` + optional `eventId`.
pub(super) fn make_agent_chunk_meta(
session_id: &str,
text: &str,
@@ -613,7 +606,6 @@ pub(super) fn make_agent_chunk_meta(
response_tx: tx,
})
}
/// `promptId`-tagged chunk (no `eventId`) — drives the viewer live-delta path.
pub(super) fn make_agent_chunk_message_with_prompt(
session_id: &str,
text: &str,
@@ -622,7 +614,6 @@ pub(super) fn make_agent_chunk_message_with_prompt(
) -> AcpClientMessage {
make_agent_chunk_meta(session_id, text, prompt_id, None, is_replay)
}
/// Live (`isReplay=false`) chunk with an optional `eventId`, for dedup tests.
pub(super) fn make_agent_chunk_with_event(
session_id: &str,
text: &str,
@@ -631,7 +622,6 @@ pub(super) fn make_agent_chunk_with_event(
) -> AcpClientMessage {
make_agent_chunk_meta(session_id, text, prompt_id, event_id, false)
}
/// Replay-marked chunk with an eventId, as `session/load` emits.
pub(super) fn replay_chunk(
session_id: &str,
text: &str,
@@ -650,7 +640,6 @@ pub(super) fn scrollback_has_system_text(agent: &mut AgentView, needle: &str) ->
)
})
}
/// `Plan` update message with the given entry contents.
pub(super) fn plan_update_msg(
session_id: &str,
entries: &[&str],
@@ -715,8 +704,6 @@ pub(super) fn xai_unhandled_notif(
std::sync::Arc::from(serde_json::value::to_raw_value(&payload).unwrap()),
)
}
/// Build an `agent_message_chunk` notification carrying both `totalTokens`
/// and an explicit `eventId`, for context/dedup interaction tests.
pub(super) fn make_token_notification_with_event(
session_id: &str,
total_tokens: u64,
@@ -741,7 +728,6 @@ pub(super) fn make_token_notification_with_event(
response_tx: tx,
})
}
/// Build an `kigi/session/prompt_complete` ext-notification for `session_id`.
pub(super) fn prompt_complete_ext(session_id: &str) -> acp::ExtNotification {
let raw = serde_json::value::to_raw_value(
&serde_json::json!({ "sessionId" : session_id, "stopReason" : "end_turn", }),
@@ -749,12 +735,9 @@ pub(super) fn prompt_complete_ext(session_id: &str) -> acp::ExtNotification {
.unwrap();
acp::ExtNotification::new("kigi/session/prompt_complete", std::sync::Arc::from(raw))
}
/// Insert a fresh agent at `id` with an optional pre-assigned session id.
pub(super) fn insert_agent(app: &mut AppView, id: AgentId, session_id: Option<&str>) {
app.agents.insert(id, make_agent(session_id));
}
/// Build an `kigi/session/prompt_complete` ext-notification with an explicit
/// `stopReason` and optional `agentResult`.
pub(super) fn prompt_complete_ext_with_reason(
session_id: &str,
stop_reason: &str,
@@ -872,7 +855,6 @@ pub(super) fn xai_wake_turn_completed_notif(
std::sync::Arc::from(serde_json::value::to_raw_value(&payload).unwrap()),
)
}
/// The newest turn-marker block on the agent's scrollback.
pub(super) fn last_marker_block(
sb: &ScrollbackState,
) -> &crate::scrollback::blocks::SessionEventBlock {
@@ -884,8 +866,6 @@ pub(super) fn last_marker_block(
})
.expect("a turn-end marker must exist")
}
/// Build a `HookExecution` update (one successful run) on the
/// `kigi/session/update` rail, optionally stamped `isReplay`.
/// `prompt_id == None` models pre-attribution shells.
pub(super) fn xai_hook_execution_notif_for_prompt(
session_id: &str,
@@ -932,7 +912,6 @@ pub(super) fn count_lifecycle_blocks(
})
.count()
}
/// Stop-hook groups on the last turn-terminal session-event marker, if any.
pub(super) fn last_marker_stop_hook_groups(
sb: &crate::scrollback::state::ScrollbackState,
) -> Option<usize> {
@@ -945,7 +924,6 @@ pub(super) fn last_marker_stop_hook_groups(
_ => None,
})
}
/// Work-only status lines ("N … still running") pushed as system rows.
pub(super) fn work_status_lines(sb: &ScrollbackState) -> Vec<String> {
(0..sb.len())
.filter_map(|i| match sb.get(i).map(|e| &e.block) {
@@ -970,12 +948,10 @@ pub(super) fn seed_two_bg_tasks_and_announce(app: &mut AppView, session_id: &str
);
app.agents.get_mut(&AgentId(0)).unwrap().end_work_announced = true;
}
/// Build an `kigi/session/interjection` ext-notification (no id).
pub(super) fn interjection_ext(session_id: &str, text: &str) -> acp::ExtNotification {
interjection_ext_with_id(session_id, text, None)
}
/// Build an `kigi/session/interjection` ext-notification with an optional
/// `interjectionId` (the originator-dedup key).
/// `interjectionId` is the originator-dedup key.
pub(super) fn interjection_ext_with_id(
session_id: &str,
text: &str,
@@ -988,7 +964,6 @@ pub(super) fn interjection_ext_with_id(
let raw = serde_json::value::to_raw_value(&payload).unwrap();
acp::ExtNotification::new("kigi/session/interjection", std::sync::Arc::from(raw))
}
/// Text of the most recent user prompt block in scrollback, if any.
/// Interjections render as standard user prompt blocks.
pub(super) fn last_interjection_text(sb: &ScrollbackState) -> Option<String> {
(0..sb.len())
@@ -1008,7 +983,6 @@ pub(super) fn switch_active_to(app: &mut AppView, id: AgentId) {
crate::app::dispatch::SwitchCause::Picker,
);
}
/// Concatenate the text of every `AgentMessage` block in this view's scrollback.
pub(super) fn agent_message_text(view: &AgentView) -> String {
let mut out = String::new();
for i in 0..view.scrollback.len() {
@@ -1020,7 +994,6 @@ pub(super) fn agent_message_text(view: &AgentView) -> String {
}
out
}
/// Build a `Plan` notification with one entry per `entries` string.
pub(super) fn make_plan_message(session_id: &str, entries: &[&str]) -> AcpClientMessage {
let (tx, _rx) = tokio::sync::oneshot::channel();
let plan_entries = entries
@@ -1040,7 +1013,6 @@ pub(super) fn make_plan_message(session_id: &str, entries: &[&str]) -> AcpClient
response_tx: tx,
})
}
/// Build an `AvailableCommandsUpdate` notification with the given command names.
pub(super) fn make_commands_update_message(
session_id: &str,
names: &[&str],
@@ -1061,8 +1033,6 @@ pub(super) fn make_commands_update_message(
response_tx: tx,
})
}
/// Build a `ToolCallUpdate` notification carrying a Bash `raw_output`
/// chunk for `tool_call_id`. Used to drive the bg-task stdout route.
pub(super) fn make_bash_stdout_message(
session_id: &str,
tool_call_id: &str,
@@ -1090,7 +1060,6 @@ pub(super) fn make_bash_stdout_message(
response_tx: tx,
})
}
/// Build an `ExtNotification` envelope for `kigi/session_notification`.
pub(super) fn make_ext_session_notification(
session_id: &str,
update: XaiSessionUpdate,
@@ -1101,7 +1070,6 @@ pub(super) fn make_ext_session_notification(
update,
)
}
/// Build an `ExtNotification` envelope with an explicit xAI session method.
pub(super) fn make_ext_session_notification_with_method(
session_id: &str,
method: &str,
@@ -1179,7 +1147,6 @@ pub(super) fn test_subagent_progress(
error_count: 0,
}
}
/// Snapshot of subagent state after SubagentSpawned for method-parity tests.
pub(super) struct SubagentSpawnSnapshot {
description: String,
subagent_type: String,
@@ -1210,7 +1177,6 @@ pub(super) fn snapshot_after_subagent_spawn(
scrollback_entry_id: info.scrollback_entry_id,
}
}
/// Snapshot after SubagentFinished for method-parity tests.
pub(super) struct SubagentFinishSnapshot {
finished: bool,
status: Option<String>,
@@ -1427,8 +1393,6 @@ pub(super) fn dispatch_goal_update(
app,
)
}
/// Build + dispatch a `GoalUpdated` for `sess-A` with the given id /
/// status / elapsed; returns whether the notification requested a redraw.
pub(super) fn send_goal_update(
app: &mut AppView,
goal_id: &str,
@@ -1437,8 +1401,6 @@ pub(super) fn send_goal_update(
) -> bool {
dispatch_goal_update(app, goal_update_value(goal_id, status, elapsed_ms))
}
/// Build a minimal `RequestPermission` message that carries `session_id`
/// and one `AllowOnce` option.
pub(super) fn make_permission_message(
session_id: &str,
) -> (
@@ -1543,8 +1505,6 @@ pub(super) fn make_replayed_task_backgrounded_notif(
let raw = serde_json::value::to_raw_value(&notif).unwrap();
acp::ExtNotification::new("kigi/session/update", std::sync::Arc::from(raw))
}
/// Register a pending Execute tool call in the tracker and send an InProgress
/// update to create the scrollback entry. Returns the agent for further use.
pub(super) fn setup_pending_execute_tool(app: &mut AppView, tc_id: &str) {
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
let meta = crate::acp::meta::NotificationMeta::default();
@@ -1568,7 +1528,6 @@ pub(super) fn setup_pending_execute_tool(app: &mut AppView, tc_id: &str) {
);
agent.session.tracker.handle_update(update, &meta, &mut agent.scrollback);
}
/// Send a late InProgress update with is_background=true to trigger late bg detection.
pub(super) fn send_late_bg_detection(app: &mut AppView, tc_id: &str) {
use serde_json::json;
use kigi_tools::types::output::{BashOutput, ToolOutput};
@@ -1728,9 +1687,7 @@ pub(super) fn make_reasoning_models_update_notif(
acp::ExtNotification::new("kigi/models/update", std::sync::Arc::from(raw))
}
/// Seed a session's model catalog with the given ids and mark
/// `current_model_id` as the active one (must be in the list). Used by
/// the `ModelChanged` broadcast tests to set up a starting state that
/// the simulated remote/local switch then transitions away from.
/// `current_model_id` as the active one (must be in the list).
pub(super) fn seed_models(agent: &mut AgentView, current: &str, available: &[&str]) {
for id in available {
let model_id = acp::ModelId::new(std::sync::Arc::from(*id));
@@ -1796,7 +1753,6 @@ pub(super) fn make_current_mode_update(mode_id: &str) -> acp::SessionUpdate {
acp::CurrentModeUpdate::new(acp::SessionModeId::new(mode_id)),
)
}
/// Helper: build an `kigi/mcp/init_progress` notification.
pub(super) fn make_mcp_init_progress_notif(
total: u32,
connected: u32,
@@ -1865,8 +1821,6 @@ pub(super) fn make_servers_updated_notif() -> acp::ExtNotification {
let raw = serde_json::value::to_raw_value(&payload).unwrap();
acp::ExtNotification::new("kigi/mcp/servers_updated", std::sync::Arc::from(raw))
}
/// Real post-handshake / auth-recovery wire shape:
/// `McpToolsChanged { sessionId, serverName, tools }`.
pub(super) fn make_tools_changed_notif_post_h2(
session_id: &str,
) -> acp::ExtNotification {
@@ -1886,8 +1840,6 @@ pub(super) fn make_tools_changed_notif_pre_h2() -> acp::ExtNotification {
let raw = serde_json::value::to_raw_value(&payload).unwrap();
acp::ExtNotification::new("kigi/mcp/tools_changed", std::sync::Arc::from(raw))
}
/// Real `mcp_initialized` wire shape:
/// `{ sessionId, mcpToolCount, elapsedMs }`.
pub(super) fn make_mcp_initialized_notif(session_id: &str) -> acp::ExtNotification {
let payload = serde_json::json!(
{ "sessionId" : session_id, "mcpToolCount" : 12_u64, "elapsedMs" : 250_u64, }
@@ -1895,7 +1847,6 @@ pub(super) fn make_mcp_initialized_notif(session_id: &str) -> acp::ExtNotificati
let raw = serde_json::value::to_raw_value(&payload).unwrap();
acp::ExtNotification::new("kigi/mcp_initialized", std::sync::Arc::from(raw))
}
/// Helper: `init_progress` notification carrying an explicit sessionId.
pub(super) fn make_mcp_init_progress_notif_for(
total: u32,
connected: u32,
@@ -1909,7 +1860,6 @@ pub(super) fn make_mcp_init_progress_notif_for(
.unwrap();
acp::ExtNotification::new("kigi/mcp/init_progress", std::sync::Arc::from(raw))
}
/// Helper: `mcp_initialized` notification for a specific sessionId.
pub(super) fn make_mcp_initialized_notif_for(session_id: &str) -> acp::ExtNotification {
let raw = serde_json::value::to_raw_value(
&serde_json::json!(
@@ -186,7 +186,7 @@
agent_b.session.models.current = Some(id_5);
}
// kigi-5 removed from catalog.
// kigi-4.5 removed from catalog.
let notif = make_models_update_notif("kigi-4", &["kigi-3", "kigi-4"]);
handle_models_update(&notif, &mut app);
@@ -206,7 +206,7 @@
"agent A's model must be preserved"
);
// B's kigi-5 was removed — must fall back to shell's kigi-4, not A's kigi-3.
// B's kigi-4.5 was removed — must fall back to shell's kigi-4, not A's kigi-3.
let agent_b = app.agents.get(&AgentId(1)).unwrap();
assert_eq!(
agent_b
@@ -230,7 +230,6 @@
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
let scrollback_before = agent.scrollback.len();
// Follower: no local switch in flight.
assert!(!agent.session.model_switch_pending);
let notif = model_changed_ext("sess-1", "kigi-4", None);
@@ -326,8 +325,6 @@
let mut app = make_app_with_agent("sess-1");
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
// Invoker: a local switch is in flight (set by Action::SwitchModel /
// set_default_model before the SetSessionModelRequest is sent).
agent.session.model_switch_pending = true;
let scrollback_before = agent.scrollback.len();
@@ -45,8 +45,6 @@
let mut app = make_app_with_agent("sess-1");
// Establish the shared queue with p1 present, then put the agent in
// EditingQueued{server_id: Some("p1")}.
assert!(handle_ext_notification(
&queue_changed_ext("sess-1", &["p1"]),
&mut app
@@ -1018,8 +1016,9 @@
"adoption-on-load must not grow the scrollback"
);
// A live (non-replay) chunk stamped with the adopted prompt id now
// passes the gate and renders (previously dropped → the viewer froze).
// A live (non-replay) chunk stamped with the adopted prompt id
// passes the gate and renders; without the adoption the gate would
// drop it and the viewer would freeze.
let (tx, _rx) = tokio::sync::oneshot::channel();
let request = acp::SessionNotification::new(
acp::SessionId::new("sess-1"),
@@ -1750,8 +1749,7 @@
);
}
/// The credit-limit early return discards the popped adoption's buffer.
/// A stash whose pid replayed a durable terminal is discarded, never adopted.
/// A stash whose pid replayed a durable terminal is discarded, never adopted.
#[test]
fn terminal_in_replay_stash_is_discarded_not_adopted() {
let mut app = app_with_running_p1_and_stashed_b1();
@@ -51,7 +51,6 @@
let mut app = make_app_with_agent("sess-dedup");
let id = AgentId(0);
// First event applies (active agent → affected==true) and sets highwater.
let a1 = handle(
make_agent_chunk_with_event("sess-dedup", "hello", "p1", Some("sess-dedup-5")),
&mut app,
@@ -59,7 +58,6 @@
assert!(a1, "first event must apply");
assert_eq!(app.agents[&id].last_applied_event_seq, Some(5));
// Exact duplicate eventId → dropped (not affected), highwater unchanged.
let a2 = handle(
make_agent_chunk_with_event("sess-dedup", "hello", "p1", Some("sess-dedup-5")),
&mut app,
@@ -67,7 +65,6 @@
assert!(!a2, "a duplicate eventId must be dropped");
assert_eq!(app.agents[&id].last_applied_event_seq, Some(5));
// Stale lower eventId → dropped.
let a3 = handle(
make_agent_chunk_with_event("sess-dedup", "hello", "p1", Some("sess-dedup-3")),
&mut app,
@@ -75,7 +72,6 @@
assert!(!a3, "a lower (already-passed) eventId must be dropped");
assert_eq!(app.agents[&id].last_applied_event_seq, Some(5));
// New higher eventId → applies, highwater advances.
let a4 = handle(
make_agent_chunk_with_event("sess-dedup", "world", "p1", Some("sess-dedup-9")),
&mut app,
@@ -83,7 +79,6 @@
assert!(a4, "a new (higher) eventId must apply");
assert_eq!(app.agents[&id].last_applied_event_seq, Some(9));
// No eventId (older shell) → always applies; highwater untouched.
let a5 = handle(
make_agent_chunk_with_event("sess-dedup", "again", "p1", None),
&mut app,
@@ -101,7 +96,6 @@
fn replayed_history_with_event_id_resets_does_not_break_resume() {
let mut app = make_app_with_agent("sess-resume");
let id = AgentId(0);
// Replay arrives inside a `session/load` window.
app.agents.get_mut(&id).unwrap().session.loading_replay = true;
// eventIds climb (5, 9) then reset below the peak (2, 4): resumed twice.
@@ -125,7 +119,6 @@
Some("sess-resume-4"),
"the reconnect cursor follows the last APPLIED event id, replay included"
);
// SessionLoaded completes the window.
app.agents.get_mut(&id).unwrap().session.loading_replay = false;
assert!(
@@ -228,13 +221,10 @@
agent.begin_session_reload(1);
}
// Partial replay lands before the load fails...
assert!(!handle(
replay_chunk("sess-rc", "h1", "sess-rc-1"),
&mut app
));
// ...along with live post-cursor traffic on BOTH streams, advancing
// both highwaters inside the doomed staging.
let _ = handle(
make_agent_chunk_with_event("sess-rc", "live tail", "p9", Some("sess-rc-40")),
&mut app,
@@ -303,7 +293,6 @@
agent.begin_session_reload(1);
}
// Post-cursor tail arrives as LIVE updates (no isReplay).
assert!(!handle(
make_agent_chunk_with_event("sess-rc", "tail", "p2", Some("sess-rc-4")),
&mut app,
@@ -375,12 +364,10 @@
1,
"gen-1 partial replay is discarded; gen-2 staging holds only the placeholder"
);
// Finalizing the dead gen-1 window is rejected.
assert!(!agent.finish_session_reload(1, true));
assert!(agent.session.loading_replay);
}
// Gen-2 load fails → the ORIGINAL transcript comes back.
let agent = app.agents.get_mut(&id).unwrap();
assert!(agent.finish_session_reload(2, false));
assert!(scrollback_has_system_text(agent, "pre-outage content"));
@@ -401,7 +388,6 @@
agent
.scrollback
.push_block(RenderBlock::system("pre-outage content"));
// In-flight fresh-view load: open batch + placeholder + replay flag.
agent.scrollback.begin_batch();
let pid = agent
.scrollback
@@ -468,7 +454,6 @@
);
app.agents.get_mut(&id).unwrap().begin_session_reload(1);
// Replayed Plan overwrites the (fresh) live pane during the window.
let _ = handle(
plan_update_msg("sess-todo", &["replayed-task"], Some("sess-todo-2"), true),
&mut app,
@@ -494,7 +479,6 @@
);
app.agents.get_mut(&id).unwrap().begin_session_reload(1);
// A LIVE tail Plan applied in-window is newer than the stash.
let _ = handle(
plan_update_msg("sess-todo", &["tail-task"], Some("sess-todo-2"), false),
&mut app,
@@ -544,7 +528,6 @@
assert_eq!(app.agents[&id].scrollback.len(), 1);
assert_eq!(app.agents[&id].last_applied_xai_event_seq, Some(10));
// Exact re-delivery: dropped, nothing re-applied, cursor unchanged.
assert!(!handle_ext_notification(
&xai_model_switch_notif("sess-xdup", "sess-xdup-10"),
&mut app
@@ -559,7 +542,6 @@
Some("sess-xdup-10")
);
// A newer event still applies.
assert!(handle_ext_notification(
&xai_model_switch_notif("sess-xdup", "sess-xdup-11"),
&mut app
@@ -603,7 +585,6 @@
"an unhandled xAI update must not advance the dedup highwater"
);
// An applied kind (ModelAutoSwitched) advances both.
assert!(handle_ext_notification(
&xai_model_switch_notif("sess-ig", "sess-ig-8"),
&mut app
@@ -759,7 +740,6 @@
Some("sess-cur-5")
);
// Duplicate (deduped) — cursor unchanged.
assert!(!handle(
make_agent_chunk_with_event("sess-cur", "a", "p1", Some("sess-cur-5")),
&mut app,
@@ -810,7 +790,6 @@
let mut app = make_app_with_agent("sess-xai");
let id = AgentId(0);
// Replay-stamped with no load in flight → dropped, nothing pushed.
let replay_meta = serde_json::json!({ "isReplay": true, "eventId": "sess-xai-7" });
assert!(!handle_ext_notification(
&model_switch_notif(Some(replay_meta.clone())),
@@ -822,8 +801,6 @@
assert!(agent.last_seen_event_id.is_none());
}
// Same update inside a reload window → applied and marks the window
// as full-replay (finishing keeps the staged state, drops the stash).
{
let agent = app.agents.get_mut(&id).unwrap();
agent
@@ -875,7 +852,6 @@
agent.begin_session_reload(1);
}
// Replayed spawn, no finish (mirrors a mid-subagent reconnect replay).
let payload = SessionNotification {
session_id: acp::SessionId::new("sess-sub"),
update: test_subagent_spawned("sess-sub", "child-sub"),
@@ -905,8 +881,6 @@
"the child view exists and is tracked"
);
// A live child delta after the swap still renders into the child view:
// pager-side routing is intact when the leader delivers it.
let child_len_before = app.agents[&id].subagent_views["child-sub"].scrollback.len();
let _ = handle(
make_agent_chunk_with_event("child-sub", "child live text", "p-child", None),
@@ -929,7 +903,6 @@
let mut app = make_app_with_agent("sess-ctx");
let id = AgentId(0);
// Fresh live delta: high eventId, high token count.
let _ = handle(
make_token_notification_with_event("sess-ctx", 500_000, "sess-ctx-20"),
&mut app,
@@ -940,7 +913,6 @@
);
assert_eq!(app.agents[&id].last_applied_event_seq, Some(20));
// Stale historical replay delta: lower eventId (deduped), lower tokens.
let _ = handle(
make_token_notification_with_event("sess-ctx", 120_000, "sess-ctx-7"),
&mut app,
@@ -950,7 +922,6 @@
Some(500_000),
"a deduped stale delta must not regress context_used to its lower value"
);
// Highwater unchanged by the deduped event.
assert_eq!(app.agents[&id].last_applied_event_seq, Some(20));
}
@@ -961,10 +932,8 @@
fn reconnect_finalize_reload_skips_adoption_when_terminal_in_replay() {
let mut app = make_app_with_agent("sess-1");
let id = AgentId(0);
// Open a reconnect reload window (enters the replay window, clean set).
app.agents.get_mut(&id).unwrap().begin_session_reload(1);
// The running turn's terminal arrives in the reconnect replay → recorded.
let _ = handle_ext_notification(
&xai_turn_completed_notif("sess-1", "p-run", "end_turn", true),
&mut app,
@@ -999,7 +968,6 @@
seed_models(agent, "kigi-3", &["kigi-3", "kigi-4"]);
}
// Unknown model → ignored → both markers untouched.
assert!(!handle_ext_notification(
&model_changed_ext_with_event("sess-1", "kigi-99-unknown", "sess-1-7"),
&mut app
@@ -1013,7 +981,6 @@
"an ignored ModelChanged must not advance the dedup highwater"
);
// Known model → applied → both markers advance.
assert!(handle_ext_notification(
&model_changed_ext_with_event("sess-1", "kigi-4", "sess-1-8"),
&mut app
@@ -15,16 +15,12 @@
let result = handle_scheduled_task_inject_prompt(&notif, &mut app);
assert!(result);
// Agent should now be in TurnRunning (drain happened, prompt was sent).
let agent = app.agents.get(&AgentId(0)).unwrap();
assert!(agent.session.state.is_turn_running());
assert!(agent.session.pending_prompts.is_empty());
// Scrollback should have a cron prompt block.
assert!(!agent.scrollback.is_empty());
// pending_effects should contain a SendPromptBlocks with system-reminder framing,
// displayText/displayAsCron meta, and a scheduler-fired- prompt_id prefix.
match &app.pending_effects[0] {
Effect::SendPromptBlocks {
blocks, prompt_id, ..
@@ -54,10 +50,9 @@
// The leader routes `kigi/scheduled_task_inject_prompt` to the SINGLE
// session driver, so any client that receives it IS the driver and must
// enqueue + run it — even one that attached via `session/load`
// (`attached_as_viewer == true`). Previously this handler latched on
// `attached_as_viewer` and skipped, which stranded the cron loop with no
// output whenever the designated driver was an attacher (the sticky-flag
// bug). Pin the corrected behavior: the inject drives the turn.
// (`attached_as_viewer == true`). Gating the inject on that flag would
// strand the cron loop with no output when the designated driver is an
// attacher (the sticky-flag bug): the inject must drive the turn.
let mut app = make_app_with_agent("sess-1");
app.agents.get_mut(&AgentId(0)).unwrap().attached_as_viewer = true;
@@ -110,7 +105,6 @@
let result = handle_scheduled_task_inject_prompt(&notif, &mut app);
assert!(!result);
// Nothing should be enqueued.
let agent = app.agents.get(&AgentId(0)).unwrap();
assert!(agent.session.pending_prompts.is_empty());
}
@@ -126,7 +120,6 @@
let result = handle_scheduled_task_inject_prompt(&notif, &mut app);
assert!(!result);
// Agent should still be idle, nothing enqueued.
let agent = app.agents.get(&AgentId(0)).unwrap();
assert!(agent.session.state.is_idle());
}
@@ -134,7 +127,6 @@
#[test]
fn inject_prompt_busy_agent_enqueues_without_draining() {
let mut app = make_app_with_agent("sess-1");
// Make the agent busy.
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
agent.session.state = AgentState::TurnRunning;
@@ -147,14 +139,12 @@
let result = handle_scheduled_task_inject_prompt(&notif, &mut app);
assert!(result);
// Prompt should be queued but not drained (agent was busy).
let agent = app.agents.get(&AgentId(0)).unwrap();
assert_eq!(agent.session.pending_prompts.len(), 1);
assert_eq!(
agent.session.pending_prompts[0].kind,
crate::app::agent::QueueEntryKind::Cron
);
// No effects produced (drain was a no-op since agent was busy).
assert!(app.pending_effects.is_empty());
}
@@ -185,7 +175,6 @@
1
);
// A re-fire of the same task while it is still queued must not pile up.
assert!(handle_scheduled_task_inject_prompt(
&make_inject_notif(&payload),
&mut app
@@ -212,7 +201,6 @@
"humanSchedule": "every 1m",
});
// First fire on an idle agent drains into a running cron turn.
assert!(handle_scheduled_task_inject_prompt(
&make_inject_notif(&payload),
&mut app
@@ -221,7 +209,6 @@
assert!(agent.session.state.is_turn_running());
assert!(agent.session.pending_prompts.is_empty());
// A re-fire while that same loop turn is running must be skipped, not queued.
assert!(handle_scheduled_task_inject_prompt(
&make_inject_notif(&payload),
&mut app
@@ -379,7 +366,6 @@
#[test]
fn fired_updates_correct_agent_when_active_view_differs() {
let mut app = make_app_two_agents();
// Seed a known task on agent 0.
{
let agent0 = app.agents.get_mut(&AgentId(0)).unwrap();
agent0.session.scheduled_tasks.insert(
@@ -411,7 +397,6 @@
"non-active agent mutation should not trigger redraw"
);
// Agent 0's next_fire_at must be updated.
let agent0 = app.agents.get(&AgentId(0)).unwrap();
let info = agent0.session.scheduled_tasks.get("task-owner").unwrap();
assert_eq!(
@@ -420,7 +405,6 @@
"next_fire_at must update on the owning agent, not the active one"
);
// Agent 1 must be completely untouched.
let agent1 = app.agents.get(&AgentId(1)).unwrap();
assert!(
agent1.session.scheduled_tasks.is_empty(),
@@ -460,7 +444,6 @@
#[test]
fn deleted_removes_from_correct_agent_when_active_view_differs() {
let mut app = make_app_two_agents();
// Seed task on agent 0.
{
let agent0 = app.agents.get_mut(&AgentId(0)).unwrap();
agent0.session.scheduled_tasks.insert(
@@ -1,8 +1,6 @@
#![cfg_attr(rustfmt, rustfmt::skip)]
use super::*;
// ── apply_session_event ────────────────────────────────────────────
#[test]
fn apply_compaction_started_sets_activity() {
let mut session = make_session(Some("s1"));
@@ -232,7 +230,6 @@
Some("legacy_auth"),
"Unauthorized (401) ... deprecated authentication method"
));
// Unrelated failures must not be treated as re-authable.
assert!(!is_reauthable_failure(
Some("server_error"),
"internal server error"
@@ -339,7 +336,6 @@
));
}
/// Non-auth terminal failures still render the standard RetryFailed.
#[test]
fn apply_retry_state_generic_failure_still_shows_retry_failed() {
let mut session = make_session(Some("s1"));
@@ -544,8 +540,6 @@
assert!(!apply_session_event(&update, &mut session, &mut scrollback, false));
}
// ── handle_child_session_notification ──────────────────────────────
#[test]
fn child_compact_completed_updates_subagent_info() {
let mut agent = make_agent(Some("root-sess"));
@@ -616,7 +610,6 @@
#[test]
fn child_notification_without_view_returns_false() {
let mut agent = make_agent(Some("root-sess"));
// No child view registered.
let update = XaiSessionUpdate::AutoCompactStarted {
tokens_used: 90000,
context_window: 131072,
@@ -659,8 +652,6 @@
assert!(!changed);
}
// ── apply_retry_state ─────────────────────────────────────────────
#[test]
fn retry_failed_encrypted_content_sets_model_incompatible() {
use kigi_shell::extensions::notification::RetryState;
@@ -4,9 +4,9 @@
#[test]
fn acp_chunk_for_inactive_agent_lands_in_its_scrollback() {
// Regression: switching away from a streaming agent must not
// discard chunks bound for that agent. Before this fix, only
// `TaskResult::PromptResponse` survived, so the user saw a bare
// "Worked for X.Xs" with no body text.
// discard chunks bound for that agent. If only
// `TaskResult::PromptResponse` were routed, the user would see a
// bare "Worked for X.Xs" with no body text.
let mut app = make_app_with_agent("sess-A");
insert_agent(&mut app, AgentId(1), Some("sess-B"));
switch_active_to(&mut app, AgentId(1));
@@ -32,7 +32,6 @@
#[test]
fn acp_chunk_for_active_agent_returns_affected_true() {
// Baseline: chunk for the visible agent triggers a redraw.
let mut app = make_app_with_agent("sess-A");
insert_agent(&mut app, AgentId(1), Some("sess-B"));
switch_active_to(&mut app, AgentId(1));
@@ -46,9 +45,6 @@
#[test]
fn acp_chunk_for_subagent_routes_through_parent() {
// Subagent (child) chunk must land in the parent's
// `subagent_views[child_sid]` even when a different agent is
// currently active.
let mut app = make_app_with_agent("sess-A");
insert_agent(&mut app, AgentId(1), Some("sess-B"));
switch_active_to(&mut app, AgentId(1));
@@ -169,7 +165,6 @@
let mut app = make_app_with_agent("sess-A");
insert_agent(&mut app, AgentId(1), Some("sess-B"));
switch_active_to(&mut app, AgentId(1));
// Sanity: A's todo starts empty.
assert_eq!(
app.agents.get(&AgentId(0)).unwrap().todo.counts().total(),
0,
@@ -277,9 +272,6 @@
#[test]
fn acp_chunks_for_two_agents_dont_cross_contaminate() {
// Send chunks to both A and B in sequence; each landing in its own
// scrollback proves the demux works in both directions regardless
// of which agent is currently active.
let mut app = make_app_with_agent("sess-A");
insert_agent(&mut app, AgentId(1), Some("sess-B"));
switch_active_to(&mut app, AgentId(1));
@@ -154,8 +154,8 @@
}
/// The live-refresh flip mirrors `set_group_tool_verbs_inner`'s stale
/// group-expansion cleanup: a previously expanded verb slot must not
/// survive a remote flip as an expanded header.
/// group-expansion cleanup: a verb slot expanded before the flip must
/// not survive it as an expanded header.
#[test]
fn settings_update_flip_resets_stale_group_expansion() {
crate::appearance::cache::set_group_tool_verbs(true);
@@ -204,16 +204,16 @@
// Two agents both in auto; the active tab's global mirror reads "ask"
// (a tab switch / Shift+Tab re-anchored it away from auto). A
// mid-session gate kill-switch (`auto_permission_mode_enabled=false`)
// must clear the per-session auto flag on BOTH agents. The old code
// gated this fan-out on `current_ui.permission_mode == "auto"`, so it
// skipped background agents and left stale `auto_mode` that
// must clear the per-session auto flag on BOTH agents. Gating the
// fan-out on `current_ui.permission_mode == "auto"` would skip
// background agents and leave stale `auto_mode` that
// `switch_to_agent` could re-anchor back to "auto" on return.
let mut app = make_app_two_agents();
app.auto_mode_gate = true;
for agent in app.agents.values_mut() {
agent.session.auto_mode = true;
}
// Active tab's mirror is NOT "auto" — the old bug's skip condition.
// Active tab's mirror is NOT "auto" — reproduces the skip condition.
app.current_ui.permission_mode = Some("ask".into());
let killswitch = acp::ExtNotification::new(
@@ -263,7 +263,6 @@
let _ = handle_ext_notification(&killswitch, &mut app);
assert!(!app.auto_mode_gate, "gate must be off after kill-switch");
// Sibling always-approve is untouched — the kill-switch clears only auto.
assert!(
app.agents[&AgentId(2)].session.is_yolo(),
"sibling always-approve must stay yolo after the auto kill-switch"
@@ -538,7 +538,6 @@
});
}
/// Live spawn: meta prompt without updates.jsonl still injects the task once.
#[test]
fn subagent_spawn_live_injects_meta_prompt_once_without_updates() {
with_replay_disk_home(|home| {
@@ -54,7 +54,7 @@
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
agent.session.start_turn(&mut agent.scrollback);
agent.session.current_prompt_id = Some("pid-stuck".into());
agent.session.cancel_turn(&mut agent.scrollback); // CancelTurn → TurnCancelling
agent.session.cancel_turn(&mut agent.scrollback);
assert!(!agent.attached_as_viewer);
}