Files
Kigi-CLI/crates/common/kigi-compaction/src/reminder.rs
T
ZacharyZhang-NY 6f31415ed6 §9 acceptance: grep-zero sweep — every internal x.ai/grok identifier renamed
The PRD's first acceptance gate now holds: grep -RinE '\bx\.ai\b|grok'
crates/ --include='*.rs' → 0 matches (exempt: NOTICE and third-party
license archives, README provenance, and the required 'Based on Grok
Build Open Source' attribution, now sourced from version_attribution.txt).

Wire-visible renames (both sides in this repo, changed in lockstep):
- Auth method id 'grok.com' → 'kimi-code' (AuthMethodKind::KimiCode).
- Every x.ai/* and _x.ai/* ACP ext method and meta key → kigi/* /
  _kigi/* (~200 names; grokShell → kigiShell). Session-file replay keeps
  a read-side alias for the legacy '_x.ai/session/update' method so
  existing updates.jsonl histories load; writes emit only the new name
  (both directions test-pinned).
- Agent types grok-build* → kigi* with a documented legacy-prefix alias
  at resolution time so persisted sessions keep resolving.
- ToolNamespace/BuiltinAgentName GrokBuild* → Kigi* (wire snake_case
  kigi/kigi_concise/kigi_hashline; schema regenerated); grok_build
  implementation dirs renamed to kigi*.
- x-grok-* headers → x-kigi-*, __GROK_* sentinels → __KIGI_*, themes
  grokday/groknight → kigiday/kiginight (old persisted values fall back
  to the default theme), web_fetch allowlist xAI hosts → kimi.com +
  moonshot platforms, changelog CDN → this repo, grok-build changelog
  archives deleted.
- BYOK default endpoint removed: [endpoints] api_base_url is now truly
  optional with NO default — consumers fail fast with the flag name when
  unset (no silent x.ai egress). Mock harnesses inject it explicitly.
- System-prompt identity fixed: 'released by xAI' → 'an unofficial
  community CLI for Kimi' (template + regenerated encrypted form).

Also repaired pre-existing grok-era test debt found by the sweep: the
stale trace_classify default-model pin, the grok-pager UA label test,
pty-harness stale-binary reuse and non-hermetic moonshot routing (a PTY
test could previously reach the real api.moonshot.cn), and the outdated
oauth fixture scope key.

Gates: §9 grep 0; fmt clean; workspace check/clippy 0/0 (-D warnings);
FULL cargo test --workspace: 234 suites, 21,961 passed, 0 failed;
deny advisories ok.
2026-07-18 02:48:46 -04:00

520 lines
17 KiB
Rust

//! Shared post-compaction reminder helpers (host-agnostic).
//!
//! Lives at the crate root rather than under a compaction-style submodule
//! because it is consumed by *both* compaction styles and both harnesses:
//!
//! - Kigi chat intra FullReplace ([`crate::intra_compaction`]) and inter
//! (appends after sampling via [`append_reminder_block`])
//! - kigi full-replace ([`crate::code_compaction`] assemble's
//! `system_reminder`)
//!
//! **What lives here:** pure formatting of the three **common** active-agent
//! sections — Running Background Tasks, TODO List, Running Subagents — plus
//! `<system-reminder>` wrapping and summary append.
//!
//! **What stays in the product host:** snapshotting, tool-name resolution, and harness-only
//! sections (files, AGENTS.md, skills, MCP, memory). Callers pass **borrowed
//! views** (`&str` over live state) so long fields (commands, todo content,
//! descriptions, ids) are not cloned just to format.
// ---------------------------------------------------------------------------
// Borrowed views over harness live state (no long-string clones)
// ---------------------------------------------------------------------------
/// Model-facing poll/cancel tool names from the current toolset.
/// Never hard-code: a client manifest can rename them.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SubagentToolNames<'a> {
pub poll: &'a str,
pub cancel: &'a str,
}
/// Status of a todo item in the post-compaction reminder.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TodoStatus {
Pending,
InProgress,
Completed,
Cancelled,
}
impl TodoStatus {
pub fn is_actionable(self) -> bool {
matches!(self, Self::Pending | Self::InProgress)
}
pub fn tag(self) -> &'static str {
match self {
Self::Pending => "[pending]",
Self::InProgress => "[in_progress]",
Self::Completed => "[completed]",
Self::Cancelled => "[cancelled]",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TodoItem<'a> {
pub id: &'a str,
pub content: &'a str,
pub status: TodoStatus,
}
/// Still-running background task. `task_id` is rendered verbatim.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BackgroundTask<'a> {
pub task_id: &'a str,
pub command: &'a str,
/// Parenthetical status (typically `"running"`).
pub status: &'a str,
pub tool_name: Option<&'a str>,
}
/// Still-running sub-agent. `subagent_id` is rendered verbatim.
///
/// `subagent_type` / `description` are optional so chat (no type, optional
/// desc) and build (both present) share one line format.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RunningSubagent<'a> {
pub subagent_id: &'a str,
pub subagent_type: Option<&'a str>,
pub description: Option<&'a str>,
pub elapsed_secs: u64,
}
/// Borrowed active-agent state for reminder rendering.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ActiveAgentReminderState<'a> {
pub running_commands: &'a [BackgroundTask<'a>],
pub todos: &'a [TodoItem<'a>],
pub running_subagents: &'a [RunningSubagent<'a>],
}
impl ActiveAgentReminderState<'_> {
pub fn is_empty(&self) -> bool {
self.running_commands.is_empty()
&& self.running_subagents.is_empty()
&& !self.has_actionable_todos()
}
pub fn has_actionable_todos(&self) -> bool {
self.todos.iter().any(|t| t.status.is_actionable())
}
}
// ---------------------------------------------------------------------------
// Section formatters
// ---------------------------------------------------------------------------
/// `## Running Background Tasks`, or `None` when empty.
pub fn section_background_tasks(tasks: &[BackgroundTask<'_>]) -> Option<String> {
if tasks.is_empty() {
return None;
}
let lines = tasks
.iter()
.map(|t| match t.tool_name {
Some(tool) => format!(
"- \"{}\": `{}` ({}, {})",
t.task_id, t.command, t.status, tool
),
None => format!("- \"{}\": `{}` ({})", t.task_id, t.command, t.status),
})
.collect::<Vec<_>>()
.join("\n");
Some(format!(
"## Running Background Tasks\n\
These tasks are still running:\n{lines}"
))
}
/// `## TODO List` for actionable items, or `None` when none. Completed/
/// cancelled collapse to a count trailer.
pub fn section_todo_list(todos: &[TodoItem<'_>]) -> Option<String> {
let active: Vec<_> = todos
.iter()
.filter(|t| t.status.is_actionable())
.map(|t| format!("- {} {}: {}", t.status.tag(), t.id, t.content))
.collect();
if active.is_empty() {
return None;
}
let completed = todos
.iter()
.filter(|t| t.status == TodoStatus::Completed)
.count();
let cancelled = todos
.iter()
.filter(|t| t.status == TodoStatus::Cancelled)
.count();
let trailer = match (completed, cancelled) {
(0, 0) => String::new(),
(c, 0) => format!("\n({c} completed)"),
(0, k) => format!("\n({k} cancelled)"),
(c, k) => format!("\n({c} completed, {k} cancelled)"),
};
Some(format!(
"## TODO List\n\
This is your task list from before the conversation was compacted — it is still \
active. Keep working through the items below and update their status as you make \
progress:\n{}{trailer}",
active.join("\n"),
))
}
/// `## Running Subagents`, or `None` when empty. Omit entirely when tool
/// names cannot be resolved rather than point at wrong names.
pub fn section_running_subagents(
subagents: &[RunningSubagent<'_>],
tools: &SubagentToolNames<'_>,
) -> Option<String> {
if subagents.is_empty() {
return None;
}
let lines = subagents
.iter()
.map(format_subagent_line)
.collect::<Vec<_>>()
.join("\n");
Some(format!(
"## Running Subagents\n\
These subagents were launched before this compaction and are still running. \
Use `{}` with the subagent_id to check their status or retrieve results. \
Use `{}` with the subagent_id to cancel a subagent.\n{lines}",
tools.poll, tools.cancel
))
}
fn format_subagent_line(s: &RunningSubagent<'_>) -> String {
let mut head = format!("subagent_id: `{}`", s.subagent_id);
if let Some(ty) = s.subagent_type {
head.push_str(", type: `");
head.push_str(ty);
head.push('`');
}
if let Some(desc) = s.description {
head.push_str(", task: \"");
head.push_str(desc);
head.push('"');
}
format!("- {head} (running for {}s)", s.elapsed_secs)
}
/// Common sections in order: Background Tasks → TODO → Subagents.
/// Empty kinds omitted; subagents also omitted when `subagent_tools` is `None`.
pub fn format_active_agent_sections(
state: &ActiveAgentReminderState<'_>,
subagent_tools: Option<&SubagentToolNames<'_>>,
) -> Vec<String> {
let mut sections = Vec::with_capacity(3);
if let Some(s) = section_background_tasks(state.running_commands) {
sections.push(s);
}
if let Some(s) = section_todo_list(state.todos) {
sections.push(s);
}
if let Some(tools) = subagent_tools
&& let Some(s) = section_running_subagents(state.running_subagents, tools)
{
sections.push(s);
}
sections
}
/// Wrap non-empty sections in `<system-reminder>…</system-reminder>`.
pub fn wrap_system_reminder(sections: impl IntoIterator<Item = impl AsRef<str>>) -> Option<String> {
let mut body = String::new();
for s in sections {
let s = s.as_ref();
if s.trim().is_empty() {
continue;
}
if !body.is_empty() {
body.push_str("\n\n");
}
body.push_str(s);
}
if body.is_empty() {
None
} else {
Some(format!("<system-reminder>\n{body}\n</system-reminder>"))
}
}
/// Full active-agent-state `<system-reminder>`, or `None` when nothing to preserve.
pub fn format_active_agent_reminder(
state: &ActiveAgentReminderState<'_>,
subagent_tools: Option<&SubagentToolNames<'_>>,
) -> Option<String> {
wrap_system_reminder(format_active_agent_sections(state, subagent_tools))
}
// ---------------------------------------------------------------------------
// Summary injection
// ---------------------------------------------------------------------------
/// Append a trailing block to a compaction summary, separated by a blank line.
/// Returns `summary` unchanged when `reminder` is `None` or blank.
pub fn append_reminder_block(summary: String, reminder: Option<&str>) -> String {
match reminder {
Some(reminder) if !reminder.trim().is_empty() => format!("{summary}\n\n{reminder}"),
_ => summary,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn tools_native() -> SubagentToolNames<'static> {
SubagentToolNames {
poll: "get_task_output",
cancel: "kill_task",
}
}
fn tools_renamed() -> SubagentToolNames<'static> {
SubagentToolNames {
poll: "get_command_or_subagent_output",
cancel: "kill_command_or_subagent",
}
}
#[test]
fn empty_state_is_none() {
assert!(
format_active_agent_reminder(
&ActiveAgentReminderState::default(),
Some(&tools_native())
)
.is_none()
);
}
#[test]
fn missing_tool_names_omits_subagent_section_only() {
let agents = [RunningSubagent {
subagent_id: "sa-1",
subagent_type: None,
description: Some("x"),
elapsed_secs: 1,
}];
let state = ActiveAgentReminderState {
running_subagents: &agents,
..Default::default()
};
assert!(format_active_agent_reminder(&state, None).is_none());
let cmds = [BackgroundTask {
task_id: "bg-1",
command: "npm run dev",
status: "running",
tool_name: Some("run_terminal_command"),
}];
let state = ActiveAgentReminderState {
running_commands: &cmds,
..Default::default()
};
let out = format_active_agent_reminder(&state, None).expect("reminder");
assert!(out.contains("## Running Background Tasks"));
assert!(!out.contains("## Running Subagents"));
}
#[test]
fn renders_chat_style_subagent_ids_verbatim() {
let agents = [
RunningSubagent {
subagent_id: "019ea7f0-cb66-7aa2-9a09-488a3a795795",
subagent_type: None,
description: Some("deploy staging"),
elapsed_secs: 42,
},
RunningSubagent {
subagent_id: "sa-2",
subagent_type: None,
description: None,
elapsed_secs: 5,
},
];
let state = ActiveAgentReminderState {
running_subagents: &agents,
..Default::default()
};
let out = format_active_agent_reminder(&state, Some(&tools_native())).expect("reminder");
assert!(out.starts_with("<system-reminder>"));
assert!(out.ends_with("</system-reminder>"));
assert!(out.contains("subagent_id: `019ea7f0-cb66-7aa2-9a09-488a3a795795`"));
assert!(out.contains("task: \"deploy staging\" (running for 42s)"));
assert!(out.contains("subagent_id: `sa-2` (running for 5s)"));
assert!(!out.contains("task-019ea7f0"));
assert!(!out.contains("type:"));
}
#[test]
fn renders_build_style_subagent_with_type() {
let agents = [RunningSubagent {
subagent_id: "sub-1",
subagent_type: Some("explore"),
description: Some("find files"),
elapsed_secs: 5,
}];
let state = ActiveAgentReminderState {
running_subagents: &agents,
..Default::default()
};
let out = format_active_agent_reminder(&state, Some(&tools_renamed())).expect("reminder");
assert!(out.contains(
"- subagent_id: `sub-1`, type: `explore`, task: \"find files\" (running for 5s)"
));
}
#[test]
fn uses_renamed_tool_names_verbatim() {
let agents = [RunningSubagent {
subagent_id: "sa-1",
subagent_type: None,
description: Some("x"),
elapsed_secs: 1,
}];
let state = ActiveAgentReminderState {
running_subagents: &agents,
..Default::default()
};
let out = format_active_agent_reminder(&state, Some(&tools_renamed())).expect("reminder");
assert!(out.contains("get_command_or_subagent_output"));
assert!(!out.contains("get_task_output"));
}
#[test]
fn renders_background_tasks() {
let cmds = [
BackgroundTask {
task_id: "019f1723-a9f0-76f2-98ae-56af965922f6",
command: "npm run dev",
status: "running",
tool_name: Some("run_terminal_command"),
},
BackgroundTask {
task_id: "bg-2",
command: "cargo watch -x test",
status: "running",
tool_name: None,
},
];
let state = ActiveAgentReminderState {
running_commands: &cmds,
..Default::default()
};
let out = format_active_agent_reminder(&state, None).expect("reminder");
assert!(out.contains(
"- \"019f1723-a9f0-76f2-98ae-56af965922f6\": `npm run dev` (running, run_terminal_command)"
));
assert!(out.contains("- \"bg-2\": `cargo watch -x test` (running)"));
}
#[test]
fn renders_todo_list_without_tool_names() {
let todos = [
TodoItem {
id: "1",
content: "scaffold the app",
status: TodoStatus::Completed,
},
TodoItem {
id: "2",
content: "wire the API",
status: TodoStatus::InProgress,
},
TodoItem {
id: "3",
content: "write tests",
status: TodoStatus::Pending,
},
];
let state = ActiveAgentReminderState {
todos: &todos,
..Default::default()
};
let out = format_active_agent_reminder(&state, None).expect("reminder");
assert!(out.contains("- [in_progress] 2: wire the API"));
assert!(out.contains("- [pending] 3: write tests"));
assert!(out.contains("(1 completed)"));
assert!(!out.contains("scaffold the app"));
}
#[test]
fn only_completed_todos_is_none() {
let todos = [TodoItem {
id: "1",
content: "done",
status: TodoStatus::Completed,
}];
let state = ActiveAgentReminderState {
todos: &todos,
..Default::default()
};
assert!(format_active_agent_reminder(&state, None).is_none());
}
#[test]
fn section_order_background_todo_subagent() {
let cmds = [BackgroundTask {
task_id: "t1",
command: "npm run dev",
status: "running",
tool_name: None,
}];
let todos = [TodoItem {
id: "2",
content: "wire the API",
status: TodoStatus::InProgress,
}];
let agents = [RunningSubagent {
subagent_id: "sa-1",
subagent_type: None,
description: Some("deploy staging"),
elapsed_secs: 1,
}];
let state = ActiveAgentReminderState {
running_commands: &cmds,
todos: &todos,
running_subagents: &agents,
};
let out = format_active_agent_reminder(&state, Some(&tools_native())).expect("reminder");
let bg = out.find("## Running Background Tasks").expect("bg");
let todo = out.find("## TODO List").expect("todo");
let sub = out.find("## Running Subagents").expect("sub");
assert!(bg < todo && todo < sub, "order wrong:\n{out}");
}
#[test]
fn wrap_system_reminder_joins_and_skips_blank() {
let out = wrap_system_reminder(["## A\nx", "", " ", "## B\ny"]).expect("wrapped");
assert_eq!(
out,
"<system-reminder>\n## A\nx\n\n## B\ny\n</system-reminder>"
);
assert!(wrap_system_reminder(std::iter::empty::<&str>()).is_none());
}
#[test]
fn appends_after_blank_line() {
assert_eq!(
append_reminder_block("SUMMARY".to_string(), Some("REMINDER")),
"SUMMARY\n\nREMINDER"
);
}
#[test]
fn append_noop_when_none_or_blank() {
assert_eq!(
append_reminder_block("SUMMARY".to_string(), None),
"SUMMARY"
);
assert_eq!(
append_reminder_block("SUMMARY".to_string(), Some(" \n\t ")),
"SUMMARY"
);
}
}