refactor(authority): move auto-review and tool-call risk policy out of tui/ (#6935 RS-8)

The policy that decides Allow / Block / AskUser for a tool call renders
nothing, yet lived under `tui/`, so config, the engine and the subagent tools
all reached up into the terminal namespace for it. It now lives in
`core::authority`:

- `tui/auto_review.rs` -> `core/authority/auto_review.rs` (git mv, content
  unchanged apart from its one `tui::approval` import)
- `RiskLevel` / `classify_risk` move from `tui/approval/policy.rs` into
  `core/authority/risk.rs` verbatim (imports and module docs only differ), and
  are re-exported from `core::authority`
- every `crate::tui::auto_review::` reference (89) repointed

Predecessor replaced: the `tui::auto_review` module and the risk half of
`tui::approval::policy`. `ApprovalStakes` and `ToolCategory` stay with the
approval views.

Evidence (local only; no CI, provider or product run):
- `nextest -p codewhale-tui` filtered to auto_review|risk|approval|authority|
  reviewer|subagent: 1522 passed, 0 failed
- `check-runtime-contract-budget.py`: PASS, all 55 metrics exactly at budget
- `cargo fmt --all -- --check`: clean
- `grep tui::auto_review|tui::approval::{RiskLevel,classify_risk}` over
  crates/: empty

Not covered / known failing on clean HEAD, unrelated to this change (both
reproduced with RS-8 reverted):
- `event_capacity_cancelled_parallel_tool_keeps_completed_span_and_call_when_available`
  fails at test_cases_01.rs:770
- `full_access_permission_allow_runs_background_destructive_shell_without_a_hold`
  does not finish (killed at 419s)
The full workspace suite was not run.

Refs #6935

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
CodeWhale BotandClaude Sonnet 5.5 committed 2026-10-09 10:58:42 -07:00
1 parent 88d81e64a7
commit d776dcb61f
20 files changed
+452 -422

No files matched your search

+33 -23
View File
@@ -2759,14 +2759,17 @@ pub struct AutoReviewRuleConfig {
}
impl AutoReviewConfig {
fn to_runtime_policy(&self) -> crate::tui::auto_review::AutoReviewPolicy {
crate::tui::auto_review::AutoReviewPolicy {
fn to_runtime_policy(&self) -> crate::core::authority::auto_review::AutoReviewPolicy {
crate::core::authority::auto_review::AutoReviewPolicy {
allow_rules: self
.allow
.iter()
.enumerate()
.map(|(index, rule)| {
rule.to_runtime_rule(index, crate::tui::auto_review::AutoReviewAction::Allow)
rule.to_runtime_rule(
index,
crate::core::authority::auto_review::AutoReviewAction::Allow,
)
})
.collect(),
block_rules: self
@@ -2774,7 +2777,10 @@ impl AutoReviewConfig {
.iter()
.enumerate()
.map(|(index, rule)| {
rule.to_runtime_rule(index, crate::tui::auto_review::AutoReviewAction::Block)
rule.to_runtime_rule(
index,
crate::core::authority::auto_review::AutoReviewAction::Block,
)
})
.collect(),
}
@@ -2791,12 +2797,12 @@ impl AutoReviewRuleConfig {
fn to_runtime_rule(
&self,
index: usize,
action: crate::tui::auto_review::AutoReviewAction,
) -> crate::tui::auto_review::AutoReviewRule {
action: crate::core::authority::auto_review::AutoReviewAction,
) -> crate::core::authority::auto_review::AutoReviewRule {
let id_prefix = match action {
crate::tui::auto_review::AutoReviewAction::Allow => "allow",
crate::tui::auto_review::AutoReviewAction::Block => "block",
crate::tui::auto_review::AutoReviewAction::AskUser => "ask",
crate::core::authority::auto_review::AutoReviewAction::Allow => "allow",
crate::core::authority::auto_review::AutoReviewAction::Block => "block",
crate::core::authority::auto_review::AutoReviewAction::AskUser => "ask",
};
let id = self
.id
@@ -2813,14 +2819,14 @@ impl AutoReviewRuleConfig {
.map(ToOwned::to_owned)
.unwrap_or_else(|| format!("configured auto-review {id_prefix} rule"));
let mut rule = match action {
crate::tui::auto_review::AutoReviewAction::Allow => {
crate::tui::auto_review::AutoReviewRule::allow(id, reason)
crate::core::authority::auto_review::AutoReviewAction::Allow => {
crate::core::authority::auto_review::AutoReviewRule::allow(id, reason)
}
crate::tui::auto_review::AutoReviewAction::Block => {
crate::tui::auto_review::AutoReviewRule::block(id, reason)
crate::core::authority::auto_review::AutoReviewAction::Block => {
crate::core::authority::auto_review::AutoReviewRule::block(id, reason)
}
crate::tui::auto_review::AutoReviewAction::AskUser => {
crate::tui::auto_review::AutoReviewRule::block(id, reason)
crate::core::authority::auto_review::AutoReviewAction::AskUser => {
crate::core::authority::auto_review::AutoReviewRule::block(id, reason)
}
};
@@ -2893,16 +2899,20 @@ fn validate_auto_review_rules(kind: &str, rules: &[AutoReviewRuleConfig]) -> Res
Ok(())
}
fn parse_auto_review_action_kind(raw: &str) -> Option<crate::tui::auto_review::ToolActionKind> {
fn parse_auto_review_action_kind(
raw: &str,
) -> Option<crate::core::authority::auto_review::ToolActionKind> {
match raw.trim().to_ascii_lowercase().replace('-', "_").as_str() {
"read" | "mcp_read" => Some(crate::tui::auto_review::ToolActionKind::Read),
"write" => Some(crate::tui::auto_review::ToolActionKind::Write),
"shell" => Some(crate::tui::auto_review::ToolActionKind::Shell),
"read" | "mcp_read" => Some(crate::core::authority::auto_review::ToolActionKind::Read),
"write" => Some(crate::core::authority::auto_review::ToolActionKind::Write),
"shell" => Some(crate::core::authority::auto_review::ToolActionKind::Shell),
"external" | "network" | "git" | "mcp_action" | "browser" | "unknown" => {
Some(crate::tui::auto_review::ToolActionKind::External)
Some(crate::core::authority::auto_review::ToolActionKind::External)
}
"publish" => Some(crate::core::authority::auto_review::ToolActionKind::Publish),
"destructive" | "secret" => {
Some(crate::core::authority::auto_review::ToolActionKind::Destructive)
}
"publish" => Some(crate::tui::auto_review::ToolActionKind::Publish),
"destructive" | "secret" => Some(crate::tui::auto_review::ToolActionKind::Destructive),
_ => None,
}
}
@@ -4322,7 +4332,7 @@ impl Config {
}
#[must_use]
pub fn auto_review_policy(&self) -> crate::tui::auto_review::AutoReviewPolicy {
pub fn auto_review_policy(&self) -> crate::core::authority::auto_review::AutoReviewPolicy {
self.auto_review
.as_ref()
.map(AutoReviewConfig::to_runtime_policy)
+7 -7
View File
@@ -1057,10 +1057,10 @@ reason = "read_file is allowed"
config.validate()?;
let policy = config.auto_review_policy();
let shell_context = crate::tui::auto_review::AutoReviewContext::from_tool_call(
let shell_context = crate::core::authority::auto_review::AutoReviewContext::from_tool_call(
"exec_shell",
&serde_json::json!({"command": "cargo test"}),
crate::tui::auto_review::RunOrigin::Interactive,
crate::core::authority::auto_review::RunOrigin::Interactive,
codewhale_execpolicy::ApprovalMode::Auto,
true,
None,
@@ -1068,14 +1068,14 @@ reason = "read_file is allowed"
let shell_decision = policy.evaluate(&shell_context);
assert_eq!(
shell_decision.action,
crate::tui::auto_review::AutoReviewAction::Block
crate::core::authority::auto_review::AutoReviewAction::Block
);
assert_eq!(shell_decision.rule_id.as_deref(), Some("block-shell"));
let read_context = crate::tui::auto_review::AutoReviewContext::from_tool_call(
let read_context = crate::core::authority::auto_review::AutoReviewContext::from_tool_call(
"read_file",
&serde_json::json!({"path": "README.md"}),
crate::tui::auto_review::RunOrigin::Interactive,
crate::core::authority::auto_review::RunOrigin::Interactive,
codewhale_execpolicy::ApprovalMode::Auto,
true,
None,
@@ -1083,7 +1083,7 @@ reason = "read_file is allowed"
let read_decision = policy.evaluate(&read_context);
assert_eq!(
read_decision.action,
crate::tui::auto_review::AutoReviewAction::Allow
crate::core::authority::auto_review::AutoReviewAction::Allow
);
assert_eq!(read_decision.rule_id.as_deref(), Some("allow-read-file"));
@@ -1111,7 +1111,7 @@ fn auto_review_scenario() -> Result<()> {
assert_eq!(policy.block_rules.len(), 1);
assert_eq!(
policy.block_rules[0].action_kind,
Some(crate::tui::auto_review::ToolActionKind::External)
Some(crate::core::authority::auto_review::ToolActionKind::External)
);
}
// from auto_review_text_contains_fails_closed_instead_of_broadening_a_rule
+5
View File
@@ -4,6 +4,11 @@
//! in one place so prompt metadata, tool catalogs, and runtime gates cannot
//! drift independently.
pub mod auto_review;
pub mod risk;
pub use risk::{RiskLevel, classify_risk};
use std::ffi::OsStr;
use std::path::{Component, Path, PathBuf};
@@ -8,7 +8,7 @@
pub use crate::core::authority::RunOrigin;
use crate::tui::approval::{RiskLevel, ToolCategory, classify_risk, get_tool_category_for_call};
use crate::core::authority::{RiskLevel, ToolCategory, classify_risk, get_tool_category_for_call};
use codewhale_execpolicy::ApprovalMode;
use serde_json::{Value, json};
use std::borrow::Cow;
+286
View File
@@ -0,0 +1,286 @@
//! Tool-call risk policy.
//!
//! Classifies a call so the engine's gates and the approval views agree on
//! how much the call can touch. Intentionally UI-free.
use crate::tools::canonical_action::canonical_action_alias;
use codewhale_execpolicy::command_safety::is_parallel_readonly_command;
use serde_json::Value;
use super::ToolCategory;
/// Stakes-based variant for the takeover modal.
///
/// `RiskLevel::Benign` lets a single keystroke commit the approval.
/// `RiskLevel::Destructive` keeps stronger warning copy and styling
/// around approvals that can touch files, shell, or remote state.
///
/// Routing rules live in [`classify_risk`] - when in doubt, route to
/// `Destructive`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RiskLevel {
Benign,
Destructive,
}
/// Decide the stakes variant for an approval request.
///
/// The bias is conservative: a category we don't recognise routes to
/// `Destructive`, and any shell command that `command_safety` flags as
/// `Dangerous` is forced to `Destructive` even when the rest of the
/// request looks calm. The split lets the modal render stronger warning
/// copy on anything that can touch state outside this turn.
#[must_use]
pub fn classify_risk(tool_name: &str, category: ToolCategory, params: &Value) -> RiskLevel {
let tool_name = canonical_action_alias(tool_name, params);
match category {
// Read paths and discovery.
ToolCategory::Safe | ToolCategory::McpRead => RiskLevel::Benign,
// Query-only network is benign; opening a URL pulls arbitrary
// remote content, so it stays destructive.
ToolCategory::Network => match tool_name {
"web_search" | "wait_for_dev_server" | "registry_sync" => RiskLevel::Benign,
// web_run is benign for search/query, but its `open`/`click`
// actions fetch model-supplied URLs (arbitrary remote content) -
// destructive, consistent with fetch_url.
"web_run" => {
let fetches_url = params
.get("open")
.and_then(Value::as_array)
.is_some_and(|a| !a.is_empty())
|| params
.get("click")
.and_then(Value::as_array)
.is_some_and(|a| !a.is_empty());
if fetches_url {
RiskLevel::Destructive
} else {
RiskLevel::Benign
}
}
_ => RiskLevel::Destructive,
},
// Shell stays destructive unless the existing command-safety analyzer
// can prove the concrete command is read-only.
ToolCategory::Shell => {
if let Some(cmd) = params.get("command").and_then(Value::as_str)
&& is_parallel_readonly_command(cmd)
{
return RiskLevel::Benign;
}
RiskLevel::Destructive
}
// Sub-agent lifecycle: status/peek are inspection-only. Starts and
// other actions keep the explicit-options keymap (the child's own
// gates govern what it may do once running).
ToolCategory::Agent => match params.get("action").and_then(Value::as_str) {
Some("status" | "peek" | "list") => RiskLevel::Benign,
_ => RiskLevel::Destructive,
},
// File writes, MCP actions, unclassified surfaces - all require
// explicit confirmation.
ToolCategory::FileWrite | ToolCategory::McpAction | ToolCategory::Unknown => {
RiskLevel::Destructive
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::authority::{get_tool_category, get_tool_category_for_call};
use serde_json::json;
#[test]
fn classifies_read_only_surfaces_as_benign() {
for name in ["read_file", "list_dir", "list_mcp_tools", "web_search"] {
let category = get_tool_category(name);
assert_eq!(
classify_risk(name, category, &json!({})),
RiskLevel::Benign,
"{name}"
);
}
}
#[test]
fn classifies_stateful_or_unknown_surfaces_as_destructive() {
for name in [
"write_file",
"edit_file",
"apply_patch",
"mcp_linear_save_issue",
"fetch_url",
"unknown_tool",
] {
let category = get_tool_category(name);
assert_eq!(
classify_risk(name, category, &json!({})),
RiskLevel::Destructive,
"{name}"
);
}
}
#[test]
fn shell_risk_uses_command_safety_analysis() {
let category = get_tool_category("exec_shell");
assert_eq!(
classify_risk(
"exec_shell",
category,
&json!({"command": "git status --short"})
),
RiskLevel::Benign
);
assert_eq!(
classify_risk(
"exec_shell",
category,
&json!({"command": "rm -rf /tmp/example"})
),
RiskLevel::Destructive
);
}
#[test]
fn shell_exec_flags_are_not_benign() {
let category = get_tool_category("exec_shell");
for command in [
"fd -x ./pwn.sh",
"fd -uHtx ./pwn.sh",
"rg --pre /tmp/evil.sh needle .",
"git grep -O needle",
"git grep -nO needle",
] {
assert_eq!(
classify_risk("exec_shell", category, &json!({"command": command})),
RiskLevel::Destructive,
"{command} should not be classified as benign"
);
}
for command in [
"fd -e rs .",
"fd -H --type f src",
"rg needle crates/",
"git grep needle crates/",
"git grep -n needle crates/",
] {
assert_eq!(
classify_risk("exec_shell", category, &json!({"command": command})),
RiskLevel::Benign,
"{command} should remain benign"
);
}
}
#[test]
fn web_run_open_and_click_fetch_remote_content() {
let category = get_tool_category("web_run");
assert_eq!(
classify_risk(
"web_run",
category,
&json!({"search_query": [{"q": "rust"}]})
),
RiskLevel::Benign
);
assert_eq!(
classify_risk("web_run", category, &json!({"open": [{"ref_id": "x"}]})),
RiskLevel::Destructive
);
assert_eq!(
classify_risk(
"web_run",
category,
&json!({"click": [{"ref_id": "x", "id": 1}]})
),
RiskLevel::Destructive
);
}
#[test]
fn canonical_actions_keep_legacy_approval_categories_and_risk() {
let cases = [
("Bash", "run", ToolCategory::Shell, RiskLevel::Destructive),
("Bash", "wait", ToolCategory::Shell, RiskLevel::Destructive),
(
"Bash",
"interact",
ToolCategory::Shell,
RiskLevel::Destructive,
),
(
"Bash",
"cancel",
ToolCategory::Shell,
RiskLevel::Destructive,
),
("File", "read", ToolCategory::Safe, RiskLevel::Benign),
("File", "list", ToolCategory::Safe, RiskLevel::Benign),
("File", "search_name", ToolCategory::Safe, RiskLevel::Benign),
(
"File",
"search_content",
ToolCategory::Safe,
RiskLevel::Benign,
),
(
"File",
"write",
ToolCategory::FileWrite,
RiskLevel::Destructive,
),
(
"File",
"edit",
ToolCategory::FileWrite,
RiskLevel::Destructive,
),
(
"File",
"patch",
ToolCategory::FileWrite,
RiskLevel::Destructive,
),
("Git", "status", ToolCategory::Safe, RiskLevel::Benign),
("Git", "diff", ToolCategory::Safe, RiskLevel::Benign),
("Git", "log", ToolCategory::Safe, RiskLevel::Benign),
("Git", "show", ToolCategory::Safe, RiskLevel::Benign),
("Git", "blame", ToolCategory::Safe, RiskLevel::Benign),
("Git", "commit_plan", ToolCategory::Safe, RiskLevel::Benign),
(
"Run",
"tests",
ToolCategory::Unknown,
RiskLevel::Destructive,
),
(
"Run",
"verifiers",
ToolCategory::Unknown,
RiskLevel::Destructive,
),
("Web", "search", ToolCategory::Network, RiskLevel::Benign),
(
"Web",
"fetch",
ToolCategory::Network,
RiskLevel::Destructive,
),
("Web", "wait", ToolCategory::Network, RiskLevel::Benign),
];
for (family, action, expected_category, expected_risk) in cases {
let params = json!({"action": action});
let category = get_tool_category_for_call(family, &params);
assert_eq!(category, expected_category, "{family}.{action}");
assert_eq!(
classify_risk(family, category, &params),
expected_risk,
"{family}.{action}"
);
}
}
}
+17 -11
View File
@@ -355,7 +355,7 @@ pub struct EngineConfig {
/// Feature flags controlling tool availability.
pub features: Features,
/// Deterministic auto-review policy for tool calls.
pub auto_review_policy: crate::tui::auto_review::AutoReviewPolicy,
pub auto_review_policy: crate::core::authority::auto_review::AutoReviewPolicy,
/// Auto-compaction settings for long conversations.
pub compaction: CompactionConfig,
/// Shared Todo list state.
@@ -604,7 +604,7 @@ impl Default for EngineConfig {
launch_concurrency: DEFAULT_MAX_SUBAGENTS,
subagents_enabled: true,
features: Features::with_defaults(),
auto_review_policy: crate::tui::auto_review::AutoReviewPolicy::default(),
auto_review_policy: crate::core::authority::auto_review::AutoReviewPolicy::default(),
compaction: CompactionConfig::default(),
todos: new_shared_todo_list(),
plan_state: new_shared_plan_state(),
@@ -909,7 +909,7 @@ pub struct Engine {
subagent_manager: SharedSubAgentManager,
/// The deterministic Auto-Review policy shared with every child runtime
/// so children are gated by the same rules as the parent turn.
shared_auto_review_policy: Arc<crate::tui::auto_review::AutoReviewPolicy>,
shared_auto_review_policy: Arc<crate::core::authority::auto_review::AutoReviewPolicy>,
shell_manager: SharedShellManager,
/// Read-before-edit snapshots live for the session, not for one turn's
/// transient `ToolContext` (#4475).
@@ -8945,8 +8945,8 @@ pub(crate) fn auto_review_run_origin_for_plan(detached_start: bool) -> RunOrigin
}
pub(crate) fn auto_review_plan_decision_for_context(
policy: &crate::tui::auto_review::AutoReviewPolicy,
context: &crate::tui::auto_review::AutoReviewContext<'_>,
policy: &crate::core::authority::auto_review::AutoReviewPolicy,
context: &crate::core::authority::auto_review::AutoReviewContext<'_>,
) -> (AutoReviewPlanDecision, Value) {
let decision = policy.evaluate(context);
let audit_event = policy.audit_event(context, &decision);
@@ -8959,13 +8959,17 @@ pub(crate) fn auto_review_plan_decision_for_context(
AutoReviewPlanDecision::Allow
} else {
match decision.action {
crate::tui::auto_review::AutoReviewAction::Allow
crate::core::authority::auto_review::AutoReviewAction::Allow
if context.approval_mode == ApprovalMode::Auto =>
{
AutoReviewPlanDecision::Allow
}
crate::tui::auto_review::AutoReviewAction::Allow => AutoReviewPlanDecision::NoChange,
crate::tui::auto_review::AutoReviewAction::AskUser if decision.built_in_safety_gate => {
crate::core::authority::auto_review::AutoReviewAction::Allow => {
AutoReviewPlanDecision::NoChange
}
crate::core::authority::auto_review::AutoReviewAction::AskUser
if decision.built_in_safety_gate =>
{
// Name the built-in gate honestly.
let reason = format!(
"Built-in safety gate requires approval: {}",
@@ -8983,13 +8987,15 @@ pub(crate) fn auto_review_plan_decision_for_context(
AutoReviewPlanDecision::ForcePrompt(reason)
}
}
crate::tui::auto_review::AutoReviewAction::AskUser
crate::core::authority::auto_review::AutoReviewAction::AskUser
if context.approval_mode == ApprovalMode::Auto =>
{
AutoReviewPlanDecision::ConsultReviewer(decision.reason.clone())
}
crate::tui::auto_review::AutoReviewAction::AskUser => AutoReviewPlanDecision::NoChange,
crate::tui::auto_review::AutoReviewAction::Block => {
crate::core::authority::auto_review::AutoReviewAction::AskUser => {
AutoReviewPlanDecision::NoChange
}
crate::core::authority::auto_review::AutoReviewAction::Block => {
AutoReviewPlanDecision::Block(format!(
"Auto-review policy blocked tool '{}': {}",
context.tool_name, decision.reason
+4 -4
View File
@@ -1,6 +1,6 @@
//! Model-backed Auto-Review guardian tier (v0.9.8).
//!
//! The deterministic policy engine (see [`crate::tui::auto_review`]) decides
//! The deterministic policy engine (see [`crate::core::authority::auto_review`]) decides
//! first: configured block rules and the built-in safety floor are hard
//! blocks that never reach a model. Only deterministic *fallback holds* — the
//! `AskUser` outcomes Auto posture would otherwise turn into bare permission
@@ -12,12 +12,12 @@
use std::time::Duration;
use crate::core::model_client::ModelClient;
use crate::tools::spec::ToolError;
use crate::tui::auto_review::{
use crate::core::authority::auto_review::{
AutoReviewAction, DEFAULT_GUARDIAN_POLICY, ReviewerRiskLevel, ReviewerVerdict,
parse_reviewer_verdict,
};
use crate::core::model_client::ModelClient;
use crate::tools::spec::ToolError;
use codewhale_models::Role;
use codewhale_models::{
ContentBlock, Message, MessageRequest, MessageResponse, SystemPrompt, Usage,
+1 -1
View File
@@ -4,9 +4,9 @@
//! driver is Engine::run_turn; the RPC adapter owns its bounded invocation.
use super::turn_loop::usage_has_reported_data;
use super::*;
use crate::core::authority::auto_review::AutoReviewPolicy;
use crate::rlm::bridge::{RlmUsageAccumulator, RlmUsageReservation};
use crate::rlm::turn::{RlmRoundTrace, RlmTermination, RlmTurnResult};
use crate::tui::auto_review::AutoReviewPolicy;
use anyhow::anyhow;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -1322,15 +1322,15 @@ async fn operate_conversation_reaches_provider_when_workers_are_disabled() {
}
fn auto_review_plan_decision(
policy: &crate::tui::auto_review::AutoReviewPolicy,
policy: &crate::core::authority::auto_review::AutoReviewPolicy,
tool_name: &str,
tool_input: &Value,
run_origin: crate::tui::auto_review::RunOrigin,
run_origin: crate::core::authority::auto_review::RunOrigin,
approval_mode: ApprovalMode,
workspace_trusted: bool,
workspace: Option<&Path>,
) -> (AutoReviewPlanDecision, Value) {
let context = crate::tui::auto_review::AutoReviewContext::from_tool_call(
let context = crate::core::authority::auto_review::AutoReviewContext::from_tool_call(
tool_name,
tool_input,
run_origin,
@@ -1347,10 +1347,10 @@ fn auto_review_scenario() {
// from auto_review_classifies_publish_and_holds_without_prompting
{
let (decision, audit) = auto_review_plan_decision(
&crate::tui::auto_review::AutoReviewPolicy::default(),
&crate::core::authority::auto_review::AutoReviewPolicy::default(),
"exec_shell",
&json!({"command": "git push origin main"}),
crate::tui::auto_review::RunOrigin::Interactive,
crate::core::authority::auto_review::RunOrigin::Interactive,
ApprovalMode::Auto,
true,
None,
@@ -1369,10 +1369,10 @@ fn auto_review_scenario() {
// from auto_review_classifier_allow_executes_without_prompting
{
let (decision, audit) = auto_review_plan_decision(
&crate::tui::auto_review::AutoReviewPolicy::default(),
&crate::core::authority::auto_review::AutoReviewPolicy::default(),
"read_file",
&json!({"path": "Cargo.toml"}),
crate::tui::auto_review::RunOrigin::Interactive,
crate::core::authority::auto_review::RunOrigin::Interactive,
ApprovalMode::Auto,
true,
None,
@@ -1384,10 +1384,10 @@ fn auto_review_scenario() {
// from auto_review_allows_ordinary_shell_probe_without_prompting
{
let (decision, audit) = auto_review_plan_decision(
&crate::tui::auto_review::AutoReviewPolicy::default(),
&crate::core::authority::auto_review::AutoReviewPolicy::default(),
"exec_shell",
&json!({"command": "git remote -v && git rev-parse --show-toplevel && git branch --show-current && git rev-parse HEAD && git tag --list 'v0.8.65'"}),
crate::tui::auto_review::RunOrigin::Interactive,
crate::core::authority::auto_review::RunOrigin::Interactive,
ApprovalMode::Auto,
true,
None,
@@ -1400,10 +1400,10 @@ fn auto_review_scenario() {
// from auto_review_routes_unknown_tool_to_reviewer_in_auto
{
let (decision, audit) = auto_review_plan_decision(
&crate::tui::auto_review::AutoReviewPolicy::default(),
&crate::core::authority::auto_review::AutoReviewPolicy::default(),
"mystery_tool",
&json!({"value": true}),
crate::tui::auto_review::RunOrigin::Interactive,
crate::core::authority::auto_review::RunOrigin::Interactive,
ApprovalMode::Auto,
true,
None,
@@ -1420,10 +1420,10 @@ fn auto_review_scenario() {
// from auto_review_policy_blocks_publish_when_approval_is_never
{
let (decision, audit) = auto_review_plan_decision(
&crate::tui::auto_review::AutoReviewPolicy::default(),
&crate::core::authority::auto_review::AutoReviewPolicy::default(),
"github_publish_release",
&json!({"tag": "v0.8.64"}),
crate::tui::auto_review::RunOrigin::Interactive,
crate::core::authority::auto_review::RunOrigin::Interactive,
ApprovalMode::Never,
true,
None,
@@ -1442,10 +1442,10 @@ fn auto_review_scenario() {
// from auto_review_allows_ordinary_test_command_without_prompting
{
let (decision, audit) = auto_review_plan_decision(
&crate::tui::auto_review::AutoReviewPolicy::default(),
&crate::core::authority::auto_review::AutoReviewPolicy::default(),
"exec_shell",
&json!({"command": "cargo test"}),
crate::tui::auto_review::RunOrigin::Interactive,
crate::core::authority::auto_review::RunOrigin::Interactive,
ApprovalMode::Auto,
true,
None,
@@ -1461,10 +1461,10 @@ fn auto_review_scenario() {
std::fs::create_dir(tmp.path().join(".git")).expect("git marker");
std::fs::create_dir(tmp.path().join("src")).expect("source directory");
let (decision, audit) = auto_review_plan_decision(
&crate::tui::auto_review::AutoReviewPolicy::default(),
&crate::core::authority::auto_review::AutoReviewPolicy::default(),
"write_file",
&json!({"path": "src/lib.rs", "content": "pub fn ready() {}\n"}),
crate::tui::auto_review::RunOrigin::Interactive,
crate::core::authority::auto_review::RunOrigin::Interactive,
ApprovalMode::Auto,
true,
Some(tmp.path()),
@@ -1480,10 +1480,10 @@ fn auto_review_scenario() {
std::fs::create_dir(tmp.path().join(".git")).expect("git marker");
for path in ["../outside.rs", "/etc/hostname", ".env", ".git/config"] {
let (decision, audit) = auto_review_plan_decision(
&crate::tui::auto_review::AutoReviewPolicy::default(),
&crate::core::authority::auto_review::AutoReviewPolicy::default(),
"write_file",
&json!({"path": path, "content": "blocked"}),
crate::tui::auto_review::RunOrigin::Interactive,
crate::core::authority::auto_review::RunOrigin::Interactive,
ApprovalMode::Auto,
true,
Some(tmp.path()),
@@ -338,10 +338,10 @@ fn auto_review_scenario_2() {
// from auto_review_routes_interactive_destructive_shell_to_reviewer
{
let (decision, audit) = auto_review_plan_decision(
&crate::tui::auto_review::AutoReviewPolicy::default(),
&crate::core::authority::auto_review::AutoReviewPolicy::default(),
"exec_shell",
&json!({"command": "rm -rf /"}),
crate::tui::auto_review::RunOrigin::Interactive,
crate::core::authority::auto_review::RunOrigin::Interactive,
ApprovalMode::Auto,
true,
None,
@@ -363,10 +363,10 @@ fn auto_review_scenario_2() {
("read_secret", json!({"name": "provider-token"})),
] {
let (decision, audit) = auto_review_plan_decision(
&crate::tui::auto_review::AutoReviewPolicy::default(),
&crate::core::authority::auto_review::AutoReviewPolicy::default(),
tool_name,
&input,
crate::tui::auto_review::RunOrigin::Interactive,
crate::core::authority::auto_review::RunOrigin::Interactive,
ApprovalMode::Auto,
true,
None,
@@ -386,20 +386,20 @@ fn auto_review_scenario_2() {
{
assert_eq!(
auto_review_run_origin_for_plan(false),
crate::tui::auto_review::RunOrigin::Interactive
crate::core::authority::auto_review::RunOrigin::Interactive
);
assert_eq!(
auto_review_run_origin_for_plan(true),
crate::tui::auto_review::RunOrigin::Background
crate::core::authority::auto_review::RunOrigin::Background
);
}
// from auto_review_policy_holds_background_destructive_under_suggest
{
let (decision, audit) = auto_review_plan_decision(
&crate::tui::auto_review::AutoReviewPolicy::default(),
&crate::core::authority::auto_review::AutoReviewPolicy::default(),
"exec_shell",
&json!({"command": "rm -rf ~/", "background": true}),
crate::tui::auto_review::RunOrigin::Background,
crate::core::authority::auto_review::RunOrigin::Background,
ApprovalMode::Suggest,
true,
None,
@@ -418,10 +418,10 @@ fn auto_review_scenario_2() {
// from auto_review_policy_blocks_background_destructive_under_never
{
let (decision, audit) = auto_review_plan_decision(
&crate::tui::auto_review::AutoReviewPolicy::default(),
&crate::core::authority::auto_review::AutoReviewPolicy::default(),
"exec_shell",
&json!({"command": "rm -rf ~/", "background": true}),
crate::tui::auto_review::RunOrigin::Background,
crate::core::authority::auto_review::RunOrigin::Background,
ApprovalMode::Never,
true,
None,
@@ -462,10 +462,10 @@ fn auto_review_routes_shell_commands_requiring_approval_to_reviewer() {
"cargo test $(curl https://example.com)",
] {
let (decision, audit) = auto_review_plan_decision(
&crate::tui::auto_review::AutoReviewPolicy::default(),
&crate::core::authority::auto_review::AutoReviewPolicy::default(),
"exec_shell",
&json!({"command": command}),
crate::tui::auto_review::RunOrigin::Interactive,
crate::core::authority::auto_review::RunOrigin::Interactive,
ApprovalMode::Auto,
true,
None,
@@ -492,13 +492,13 @@ fn auto_review_routes_shell_commands_requiring_approval_to_reviewer() {
#[test]
fn auto_review_plan_decision_uses_configured_policy() {
let policy = crate::tui::auto_review::AutoReviewPolicy {
let policy = crate::core::authority::auto_review::AutoReviewPolicy {
block_rules: vec![
crate::tui::auto_review::AutoReviewRule::block(
crate::core::authority::auto_review::AutoReviewRule::block(
"configured-shell-block",
"shell requires maintainer review",
)
.action_kind(crate::tui::auto_review::ToolActionKind::Shell),
.action_kind(crate::core::authority::auto_review::ToolActionKind::Shell),
],
..Default::default()
};
@@ -507,7 +507,7 @@ fn auto_review_plan_decision_uses_configured_policy() {
&policy,
"exec_shell",
&json!({"command": "cargo test"}),
crate::tui::auto_review::RunOrigin::Interactive,
crate::core::authority::auto_review::RunOrigin::Interactive,
ApprovalMode::Auto,
true,
None,
+7 -4
View File
@@ -1296,14 +1296,17 @@ impl Engine {
async fn consult_auto_review_guardian(
&self,
client: &dyn crate::core::model_client::ModelClient,
context: &crate::tui::auto_review::AutoReviewContext<'_>,
context: &crate::core::authority::auto_review::AutoReviewContext<'_>,
tool_input: &Value,
held_reason: &str,
tool_id: &str,
turn: &mut TurnContext,
) -> Result<(), ToolError> {
let context_text =
crate::tui::auto_review::build_reviewer_context(context, held_reason, tool_input);
let context_text = crate::core::authority::auto_review::build_reviewer_context(
context,
held_reason,
tool_input,
);
let _ = self
.send_event(Event::status(format!(
"Auto-Review checking '{}'",
@@ -2150,7 +2153,7 @@ impl Engine {
if blocked_error.is_none() {
let review_context =
crate::tui::auto_review::AutoReviewContext::from_tool_call_async(
crate::core::authority::auto_review::AutoReviewContext::from_tool_call_async(
&tool_name,
&tool_input,
if self.is_acp_turn() {
+3 -3
View File
@@ -2940,7 +2940,7 @@ pub struct SubAgentRuntime {
pub parent_mode: AppMode,
/// The session's deterministic Auto-Review policy (configured allow/block
/// rules plus the built-in safety floor), shared with every descendant.
pub auto_review_policy: std::sync::Arc<crate::tui::auto_review::AutoReviewPolicy>,
pub auto_review_policy: std::sync::Arc<crate::core::authority::auto_review::AutoReviewPolicy>,
/// Whether the host can answer an approval prompt for a child (an
/// interactive TUI). Headless hosts keep the fail-closed denial.
pub parent_can_prompt: bool,
@@ -3019,7 +3019,7 @@ impl SubAgentRuntime {
todos: crate::tools::todo::new_shared_todo_list(),
parent_mode: AppMode::Agent,
auto_review_policy: std::sync::Arc::new(
crate::tui::auto_review::AutoReviewPolicy::default(),
crate::core::authority::auto_review::AutoReviewPolicy::default(),
),
parent_can_prompt: false,
approval_receipt_store: None,
@@ -3056,7 +3056,7 @@ impl SubAgentRuntime {
#[must_use]
pub fn with_permission_posture(
mut self,
auto_review_policy: std::sync::Arc<crate::tui::auto_review::AutoReviewPolicy>,
auto_review_policy: std::sync::Arc<crate::core::authority::auto_review::AutoReviewPolicy>,
parent_can_prompt: bool,
) -> Self {
self.auto_review_policy = auto_review_policy;
+3 -3
View File
@@ -15397,7 +15397,7 @@ pub(crate) fn stub_runtime() -> SubAgentRuntime {
fork_context: None,
parent_mode: AppMode::Agent,
auto_review_policy: std::sync::Arc::new(
crate::tui::auto_review::AutoReviewPolicy::default(),
crate::core::authority::auto_review::AutoReviewPolicy::default(),
),
parent_can_prompt: false,
approval_receipt_store: Some(Ok(crate::approval_log::ApprovalReceiptStore::new(
@@ -23938,7 +23938,7 @@ mod child_permission_gate {
crate::approval_log::ApprovalReceiptStore::new(tmp.path().join("sessions")),
));
runtime = runtime.with_permission_posture(
std::sync::Arc::new(crate::tui::auto_review::AutoReviewPolicy::default()),
std::sync::Arc::new(crate::core::authority::auto_review::AutoReviewPolicy::default()),
parent_can_prompt,
);
let manager = Arc::clone(&runtime.manager);
@@ -25460,7 +25460,7 @@ mod child_permission_gate {
"rm -rf {}",
shlex::try_quote(&build.to_string_lossy()).unwrap()
);
// Parent classification is covered in tui::auto_review; this runtime
// Parent classification is covered in core::authority::auto_review; this runtime
// test proves the child actually executes and preserves the gate receipt.
registry
.execute("agent_gate", "bash", json!({"command": command}))
+3 -4
View File
@@ -72,10 +72,9 @@ use previews::{
pub use view::ApprovalOption;
pub use view::ApprovalView;
pub use policy::{
ApprovalStakes, RiskLevel, ToolCategory, classify_risk, classify_stakes,
get_tool_category_for_call,
};
pub use policy::{ApprovalStakes, ToolCategory, classify_stakes, get_tool_category_for_call};
use crate::core::authority::{RiskLevel, classify_risk};
/// User's decision for a pending approval
#[derive(Debug, Clone, PartialEq, Eq)]
+9 -284
View File
@@ -1,11 +1,10 @@
//! Approval risk and stakes policy.
//! Approval stakes for the takeover modal.
//!
//! This module is intentionally UI-free: it classifies tool calls so the
//! approval and elevation views can render the decision without owning the
//! policy itself.
//! The risk decision itself lives in `crate::core::authority::risk`. This maps
//! it onto the three visual weights the approval views render.
use crate::core::authority::RiskLevel;
use crate::tools::canonical_action::canonical_action_alias;
use codewhale_execpolicy::command_safety::is_parallel_readonly_command;
use serde_json::Value;
// Tool categorization is runtime policy (the extension host and auto-review
@@ -13,20 +12,6 @@ use serde_json::Value;
// approval views.
pub use crate::core::authority::{ToolCategory, get_tool_category_for_call};
/// Stakes-based variant for the takeover modal.
///
/// `RiskLevel::Benign` lets a single keystroke commit the approval.
/// `RiskLevel::Destructive` keeps stronger warning copy and styling
/// around approvals that can touch files, shell, or remote state.
///
/// Routing rules live in [`classify_risk`] - when in doubt, route to
/// `Destructive`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RiskLevel {
Benign,
Destructive,
}
/// Presentation-level stakes for the approval prompt (#3883 follow-up).
///
/// `RiskLevel` drives keymaps and stays conservative ("not provably
@@ -58,276 +43,16 @@ pub fn classify_stakes(
return ApprovalStakes::Routine;
}
let semantic_name = canonical_action_alias(tool_name, params);
match crate::tui::auto_review::ToolActionKind::from_tool_call(
match crate::core::authority::auto_review::ToolActionKind::from_tool_call(
semantic_name,
params,
category,
None,
) {
crate::tui::auto_review::ToolActionKind::Publish
| crate::tui::auto_review::ToolActionKind::Destructive => ApprovalStakes::Critical,
crate::core::authority::auto_review::ToolActionKind::Publish
| crate::core::authority::auto_review::ToolActionKind::Destructive => {
ApprovalStakes::Critical
}
_ => ApprovalStakes::Elevated,
}
}
/// Decide the stakes variant for an approval request.
///
/// The bias is conservative: a category we don't recognise routes to
/// `Destructive`, and any shell command that `command_safety` flags as
/// `Dangerous` is forced to `Destructive` even when the rest of the
/// request looks calm. The split lets the modal render stronger warning
/// copy on anything that can touch state outside this turn.
#[must_use]
pub fn classify_risk(tool_name: &str, category: ToolCategory, params: &Value) -> RiskLevel {
let tool_name = canonical_action_alias(tool_name, params);
match category {
// Read paths and discovery.
ToolCategory::Safe | ToolCategory::McpRead => RiskLevel::Benign,
// Query-only network is benign; opening a URL pulls arbitrary
// remote content, so it stays destructive.
ToolCategory::Network => match tool_name {
"web_search" | "wait_for_dev_server" | "registry_sync" => RiskLevel::Benign,
// web_run is benign for search/query, but its `open`/`click`
// actions fetch model-supplied URLs (arbitrary remote content) -
// destructive, consistent with fetch_url.
"web_run" => {
let fetches_url = params
.get("open")
.and_then(Value::as_array)
.is_some_and(|a| !a.is_empty())
|| params
.get("click")
.and_then(Value::as_array)
.is_some_and(|a| !a.is_empty());
if fetches_url {
RiskLevel::Destructive
} else {
RiskLevel::Benign
}
}
_ => RiskLevel::Destructive,
},
// Shell stays destructive unless the existing command-safety analyzer
// can prove the concrete command is read-only.
ToolCategory::Shell => {
if let Some(cmd) = params.get("command").and_then(Value::as_str)
&& is_parallel_readonly_command(cmd)
{
return RiskLevel::Benign;
}
RiskLevel::Destructive
}
// Sub-agent lifecycle: status/peek are inspection-only. Starts and
// other actions keep the explicit-options keymap (the child's own
// gates govern what it may do once running).
ToolCategory::Agent => match params.get("action").and_then(Value::as_str) {
Some("status" | "peek" | "list") => RiskLevel::Benign,
_ => RiskLevel::Destructive,
},
// File writes, MCP actions, unclassified surfaces - all require
// explicit confirmation.
ToolCategory::FileWrite | ToolCategory::McpAction | ToolCategory::Unknown => {
RiskLevel::Destructive
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::authority::get_tool_category;
use serde_json::json;
#[test]
fn classifies_read_only_surfaces_as_benign() {
for name in ["read_file", "list_dir", "list_mcp_tools", "web_search"] {
let category = get_tool_category(name);
assert_eq!(
classify_risk(name, category, &json!({})),
RiskLevel::Benign,
"{name}"
);
}
}
#[test]
fn classifies_stateful_or_unknown_surfaces_as_destructive() {
for name in [
"write_file",
"edit_file",
"apply_patch",
"mcp_linear_save_issue",
"fetch_url",
"unknown_tool",
] {
let category = get_tool_category(name);
assert_eq!(
classify_risk(name, category, &json!({})),
RiskLevel::Destructive,
"{name}"
);
}
}
#[test]
fn shell_risk_uses_command_safety_analysis() {
let category = get_tool_category("exec_shell");
assert_eq!(
classify_risk(
"exec_shell",
category,
&json!({"command": "git status --short"})
),
RiskLevel::Benign
);
assert_eq!(
classify_risk(
"exec_shell",
category,
&json!({"command": "rm -rf /tmp/example"})
),
RiskLevel::Destructive
);
}
#[test]
fn shell_exec_flags_are_not_benign() {
let category = get_tool_category("exec_shell");
for command in [
"fd -x ./pwn.sh",
"fd -uHtx ./pwn.sh",
"rg --pre /tmp/evil.sh needle .",
"git grep -O needle",
"git grep -nO needle",
] {
assert_eq!(
classify_risk("exec_shell", category, &json!({"command": command})),
RiskLevel::Destructive,
"{command} should not be classified as benign"
);
}
for command in [
"fd -e rs .",
"fd -H --type f src",
"rg needle crates/",
"git grep needle crates/",
"git grep -n needle crates/",
] {
assert_eq!(
classify_risk("exec_shell", category, &json!({"command": command})),
RiskLevel::Benign,
"{command} should remain benign"
);
}
}
#[test]
fn web_run_open_and_click_fetch_remote_content() {
let category = get_tool_category("web_run");
assert_eq!(
classify_risk(
"web_run",
category,
&json!({"search_query": [{"q": "rust"}]})
),
RiskLevel::Benign
);
assert_eq!(
classify_risk("web_run", category, &json!({"open": [{"ref_id": "x"}]})),
RiskLevel::Destructive
);
assert_eq!(
classify_risk(
"web_run",
category,
&json!({"click": [{"ref_id": "x", "id": 1}]})
),
RiskLevel::Destructive
);
}
#[test]
fn canonical_actions_keep_legacy_approval_categories_and_risk() {
let cases = [
("Bash", "run", ToolCategory::Shell, RiskLevel::Destructive),
("Bash", "wait", ToolCategory::Shell, RiskLevel::Destructive),
(
"Bash",
"interact",
ToolCategory::Shell,
RiskLevel::Destructive,
),
(
"Bash",
"cancel",
ToolCategory::Shell,
RiskLevel::Destructive,
),
("File", "read", ToolCategory::Safe, RiskLevel::Benign),
("File", "list", ToolCategory::Safe, RiskLevel::Benign),
("File", "search_name", ToolCategory::Safe, RiskLevel::Benign),
(
"File",
"search_content",
ToolCategory::Safe,
RiskLevel::Benign,
),
(
"File",
"write",
ToolCategory::FileWrite,
RiskLevel::Destructive,
),
(
"File",
"edit",
ToolCategory::FileWrite,
RiskLevel::Destructive,
),
(
"File",
"patch",
ToolCategory::FileWrite,
RiskLevel::Destructive,
),
("Git", "status", ToolCategory::Safe, RiskLevel::Benign),
("Git", "diff", ToolCategory::Safe, RiskLevel::Benign),
("Git", "log", ToolCategory::Safe, RiskLevel::Benign),
("Git", "show", ToolCategory::Safe, RiskLevel::Benign),
("Git", "blame", ToolCategory::Safe, RiskLevel::Benign),
("Git", "commit_plan", ToolCategory::Safe, RiskLevel::Benign),
(
"Run",
"tests",
ToolCategory::Unknown,
RiskLevel::Destructive,
),
(
"Run",
"verifiers",
ToolCategory::Unknown,
RiskLevel::Destructive,
),
("Web", "search", ToolCategory::Network, RiskLevel::Benign),
(
"Web",
"fetch",
ToolCategory::Network,
RiskLevel::Destructive,
),
("Web", "wait", ToolCategory::Network, RiskLevel::Benign),
];
for (family, action, expected_category, expected_risk) in cases {
let params = json!({"action": action});
let category = get_tool_category_for_call(family, &params);
assert_eq!(category, expected_category, "{family}.{action}");
assert_eq!(
classify_risk(family, category, &params),
expected_risk,
"{family}.{action}"
);
}
}
}
+2 -2
View File
@@ -19,10 +19,10 @@ use crate::tui::views::{ModalKind, ModalView, ViewAction, ViewEvent};
use crate::tui::widgets::{ApprovalWidget, Renderable};
use codewhale_localization::{Locale, MessageId, tr};
#[cfg(test)]
use super::RiskLevel;
use super::previews::exact_edit_file_preview_lines;
use super::{ApprovalRequest, ReviewDecision};
#[cfg(test)]
use crate::core::authority::RiskLevel;
/// Indices into the option list shared by both variants.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-1
View File
@@ -18,7 +18,6 @@ pub(crate) mod agent_roster;
pub mod ambient_life;
pub mod app;
pub mod approval;
pub mod auto_review;
pub mod auto_router;
pub mod automation_panel;
mod automation_routing;
+2 -3
View File
@@ -15,12 +15,11 @@ use std::time::Duration;
use crate::commands;
#[cfg(test)]
use crate::config::ProviderKind;
use crate::core::authority::{RiskLevel, ToolCategory};
#[cfg(test)]
use crate::provider_lake::all_catalog_models_for_provider;
use crate::tui::app::{App, ComposerDensity, ViewportState};
use crate::tui::approval::{
ApprovalRequest, ApprovalView, ElevationOption, ElevationRequest, RiskLevel, ToolCategory,
};
use crate::tui::approval::{ApprovalRequest, ApprovalView, ElevationOption, ElevationRequest};
use crate::tui::history::{GenericToolCell, HistoryCell, ToolCell, ToolRun, ToolStatus};
use crate::tui::menu_style;
use crate::tui::scrolling::TranscriptLineMeta;
+9 -12
View File
@@ -1,28 +1,27 @@
{
"_comment": "Runtime -> UI boundary ratchet (docs/design/TUI_DECONSTRUCTION.md, runtime split). Counts may only go down. Regenerate with python3 scripts/split/module_graph.py --update after removing references; never raise a count by hand.",
"totals": {
"prod": 69,
"test": 88,
"prod": 23,
"test": 44,
"late": 1,
"uilib": 1,
"doc": 1
"doc": 0
},
"counts": {
"prod": {
"config|tui": 23,
"core|tui": 26,
"core|tui": 6,
"fleet|lib.rs": 1,
"local_ollama|tui": 1,
"runtime_api|commands": 9,
"runtime_api|tui": 1,
"runtime_threads|lib.rs": 2,
"tools|commands": 2,
"tools|tui": 4
"tools|tui": 1
},
"test": {
"client|tui": 3,
"config|tui": 9,
"core|tui": 39,
"config|tui": 2,
"core|tui": 4,
"doctor|lib.rs": 5,
"fleet|lib.rs": 4,
"prompts|tui": 1,
@@ -30,7 +29,7 @@
"runtime_api|lib.rs": 1,
"session_manager|tui": 3,
"test_support|tui": 7,
"tools|tui": 14
"tools|tui": 12
},
"late": {
"core|exec_agent": 1
@@ -38,8 +37,6 @@
"uilib": {
"tools|crossterm": 1
},
"doc": {
"core|tui": 1
}
"doc": {}
}
}
+25 -24
View File
@@ -90,15 +90,16 @@
"modes": {
"act": {
"active": {
"bytes": 36557,
"identity_sha256": "cc8f1f208bcf83261451f1ff67f7bf65534616cfd50be0102f50aba009a9e59d",
"tokens_est": 9140,
"bytes": 37873,
"identity_sha256": "e2d6dfb7168d48ee8d2a415feac7ff5b8864e18528c233a697055370144eb138",
"tokens_est": 9469,
"tool_names": [
"agent",
"bash",
"create_goal",
"edit",
"execute_tools",
"file_search",
"get_goal",
"load_skill",
"read",
@@ -108,12 +109,12 @@
"workflow",
"write"
],
"tools": 13
"tools": 14
},
"full": {
"bytes": 85905,
"identity_sha256": "45e989bbe5ac0bb1f2d9084361c021009f30a06539f132ebd4fd2331a1bb1954",
"tokens_est": 21477,
"bytes": 85363,
"identity_sha256": "928e6417732837d26f1f26fe41ea6a11f5b8e3bfc4eeb3dd97a309379cffeff9",
"tokens_est": 21341,
"tool_names": [
"Git",
"Run",
@@ -127,7 +128,6 @@
"edit",
"execute_tools",
"file_search",
"fim_edit",
"finance",
"get_goal",
"github",
@@ -169,20 +169,21 @@
"workflow",
"write"
],
"tools": 53
"tools": 52
}
},
"operate": {
"active": {
"bytes": 36557,
"identity_sha256": "cc8f1f208bcf83261451f1ff67f7bf65534616cfd50be0102f50aba009a9e59d",
"tokens_est": 9140,
"bytes": 37873,
"identity_sha256": "e2d6dfb7168d48ee8d2a415feac7ff5b8864e18528c233a697055370144eb138",
"tokens_est": 9469,
"tool_names": [
"agent",
"bash",
"create_goal",
"edit",
"execute_tools",
"file_search",
"get_goal",
"load_skill",
"read",
@@ -192,12 +193,12 @@
"workflow",
"write"
],
"tools": 13
"tools": 14
},
"full": {
"bytes": 85905,
"identity_sha256": "45e989bbe5ac0bb1f2d9084361c021009f30a06539f132ebd4fd2331a1bb1954",
"tokens_est": 21477,
"bytes": 85363,
"identity_sha256": "928e6417732837d26f1f26fe41ea6a11f5b8e3bfc4eeb3dd97a309379cffeff9",
"tokens_est": 21341,
"tool_names": [
"Git",
"Run",
@@ -211,7 +212,6 @@
"edit",
"execute_tools",
"file_search",
"fim_edit",
"finance",
"get_goal",
"github",
@@ -253,19 +253,20 @@
"workflow",
"write"
],
"tools": 53
"tools": 52
}
},
"plan": {
"active": {
"bytes": 35027,
"identity_sha256": "df6676989a677fb08fecc8bf7ae12caf4fa88e0cae3d143cea6a4da4382b746c",
"tokens_est": 8757,
"bytes": 36343,
"identity_sha256": "6c432c75bf776d95b367b17308b171c3eaba8e70aa0c2f76bac453b3c3368a21",
"tokens_est": 9086,
"tool_names": [
"agent",
"bash",
"create_goal",
"edit",
"file_search",
"get_goal",
"load_skill",
"read",
@@ -275,12 +276,12 @@
"workflow",
"write"
],
"tools": 12
"tools": 13
},
"full": {
"bytes": 54842,
"bytes": 55333,
"identity_sha256": "ac8af1f4988199825be7b00b054c258724a44074b1d4e6de6c92ade7c1cffe63",
"tokens_est": 13711,
"tokens_est": 13834,
"tool_names": [
"Git",
"Web",