Files
Kigi-CLI/crates/codegen/kigi-tui/src/app/acp_handler/subagent_activity.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

104 lines
3.7 KiB
Rust

use super::*;
/// Update the activity label on a subagent's collapsed scrollback block.
///
/// Skips the write (and cache invalidation) when the label hasn't changed,
/// so the per-delta common case ("Responding" stays "Responding") allocates
/// nothing.
pub(super) fn sync_activity_label(
scrollback: &mut crate::scrollback::state::ScrollbackState,
entry_id: Option<crate::scrollback::entry::EntryId>,
activity_label: Option<&str>,
) {
if let Some(eid) = entry_id
&& let Some(entry) = scrollback.get_by_id_mut(eid)
&& let RenderBlock::Subagent(ref mut sb) = entry.block
&& sb.activity_label.as_deref() != activity_label
{
sb.activity_label = activity_label.map(str::to_owned);
entry.invalidate_cache();
}
}
/// Fan a subagent's computed activity label out to both surfaces that show
/// it — the collapsed scrollback block and the [`SubagentInfo`] backing the
/// tasks pane / dashboard rows — so the two can't drift.
pub(super) fn sync_subagent_activity(
parent: &mut AgentView,
child_key: &str,
activity_label: Option<String>,
) {
let Some(info) = parent.subagent_sessions.get_mut(child_key) else {
return;
};
sync_activity_label(
&mut parent.scrollback,
info.scrollback_entry_id,
activity_label.as_deref(),
);
info.activity_label = activity_label;
}
/// Resolve a subagent child view's live activity into the display label the
/// fan-out stamps ("Waiting" while the child is busy between activities).
pub(super) fn subagent_activity_label(child_view: &AgentView) -> Option<String> {
match child_view.resolve_turn_activity() {
Some(a) => Some(crate::app::subagent::format_activity_label(&a)),
None if child_view.session.state.is_busy() => Some("Waiting".to_string()),
None => None,
}
}
/// Synthesize a finish for a stuck row when a kill found nothing live to stop
/// (else `pending_kill` times out → "running"). `status` is the real terminal
/// status for an already-finished orphan, else `"cancelled"`.
pub(crate) fn finalize_killed_subagent(
app: &mut AppView,
session_id: &acp::SessionId,
subagent_id: &str,
status: &str,
) -> bool {
let Some(SessionMatch::Root(agent_id)) = find_session_match(app, session_id) else {
return false;
};
let Some(agent) = app.agents.get(&agent_id) else {
return false;
};
// Idempotency: skip if already finished.
let Some(child_session_id) = agent
.subagent_sessions
.values()
.find(|i| i.subagent_id.as_ref() == subagent_id && !i.finished)
.map(|i| i.child_session_id.to_string())
else {
return false;
};
let payload = SessionNotification {
session_id: session_id.clone(),
update: XaiSessionUpdate::SubagentFinished {
subagent_id: subagent_id.to_string(),
child_session_id,
// An already-finished orphan may be "failed", but the cancel response
// carries no failure reason (lost across the resume window), so
// `error` stays None.
status: status.to_string(),
error: None,
tool_calls: 0,
turns: 0,
// Real run time is unknown for an already-gone orphan (the row's
// started_at is stamped at resume, not the real spawn), so emit 0.
duration_ms: 0,
tokens_used: 0,
output: None,
will_wake: false,
},
meta: None,
};
let Ok(params) = serde_json::value::to_raw_value(&payload) else {
return false;
};
let notif = acp::ExtNotification::new("kigi/session/update", params.into());
handle_ext_notification(&notif, app)
}