v3.5.0: structured activity feed — stream Claude tool/command/file events as a categorized REPL conversation

Harness:
- ModelPool gains a progress channel (set_progress); chat_cli forwards it.
- New chat_claude_stream: drives Claude Code with --output-format stream-json and
  parses the event stream live — assistant text, and tool_use blocks categorized
  into tagged events (exec/danger command, read/edit file, net request/browser,
  grep/glob tool). 900s bound; clear error surfacing.
- Wired set_progress into run / whitebox / greybox.

REPL renderer (render_line):
- Tagged events render as the conversation feed: tool/command/network as compact
  CARDS (tool-runner visual), files/edits/AI text/states as iconized lines.
- Clear "what the AI is doing" states: reconning, planning, testing, validating,
  chaining, report, complete — plus a ⚠ DANGEROUS marker for risky commands.
- Untagged harness lines mapped to the same state vocabulary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
CyberSecurityUP
2026-06-24 21:04:51 -03:00
co-authored by Claude Opus 4.8
parent e8df48af9e
commit d864ea8b8a
4 changed files with 222 additions and 15 deletions
+18 -1
View File
@@ -31,6 +31,9 @@ pub struct ModelPool {
pub subscription: bool,
/// Path to an `.mcp.json` (Playwright) used on the subscription/CLI path.
pub mcp_config: Option<String>,
/// Progress channel: when set, the subscription CLI streams structured
/// activity (tools called, commands run, files read) here live.
progress: std::sync::Mutex<Option<tokio::sync::mpsc::Sender<String>>>,
}
impl ModelPool {
@@ -57,14 +60,28 @@ impl ModelPool {
},
subscription,
mcp_config,
progress: std::sync::Mutex::new(None),
}
}
/// Attach a progress channel so the subscription CLI streams structured
/// activity (commands run, files read, tools called) live.
pub fn set_progress(&self, tx: tokio::sync::mpsc::Sender<String>) {
if let Ok(mut g) = self.progress.lock() {
*g = Some(tx);
}
}
fn progress(&self) -> Option<tokio::sync::mpsc::Sender<String>> {
self.progress.lock().ok().and_then(|g| g.clone())
}
/// One completion for a model, via subscription CLI (optionally with MCP) or
/// HTTP API, with a short retry/backoff to ride out transient failures
/// (rate limits, MCP cold-starts, network blips).
async fn one(&self, m: &ModelRef, system: &str, user: &str) -> Result<String> {
let use_cli = self.subscription && cli_binary_for(&m.provider).is_some();
let progress = self.progress();
let mut last = anyhow::anyhow!("no attempt");
for attempt in 0..3u64 {
if attempt > 0 {
@@ -73,7 +90,7 @@ impl ModelPool {
}
let r = if use_cli {
self.client
.chat_cli(&m.provider, &m.model, system, user, self.mcp_config.as_deref())
.chat_cli(&m.provider, &m.model, system, user, self.mcp_config.as_deref(), progress.clone())
.await
} else {
self.client.chat(m, system, user).await