mirror of
https://github.com/instructkr/claw-code.git
synced 2026-06-30 14:00:23 -04:00
Compare commits
15 Commits
b1d76983d2
...
54269da157
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54269da157 | ||
|
|
f741a42507 | ||
|
|
6b3e2d8854 | ||
|
|
1a8f73da01 | ||
|
|
7d9f11b91f | ||
|
|
8e1bca6b99 | ||
|
|
8d0308eecb | ||
|
|
4d10caebc6 | ||
|
|
414526c1bd | ||
|
|
2a2e205414 | ||
|
|
c55c510883 | ||
|
|
3fe0caf348 | ||
|
|
47086c1c14 | ||
|
|
e579902782 | ||
|
|
ca8950c26b |
23
README.md
23
README.md
@@ -49,22 +49,27 @@ The canonical implementation lives in [`rust/`](./rust), and the current source
|
||||
> **`cargo install clawcode` will not work** — this package is not published on crates.io. Build from source as shown below.
|
||||
|
||||
```bash
|
||||
# 1. Clone and build
|
||||
git clone https://github.com/ultraworkers/claw-code
|
||||
cd claw-code/rust
|
||||
cargo build --workspace
|
||||
./target/debug/claw --help
|
||||
./target/debug/claw prompt "summarize this repository"
|
||||
```
|
||||
|
||||
Authenticate with either an API key or the built-in OAuth flow:
|
||||
|
||||
```bash
|
||||
# 2. Set your API key (Anthropic API key — not a Claude subscription)
|
||||
export ANTHROPIC_API_KEY="sk-ant-..."
|
||||
# or
|
||||
cd rust
|
||||
./target/debug/claw login
|
||||
|
||||
# 3. Verify everything is wired correctly
|
||||
./target/debug/claw doctor
|
||||
|
||||
# 4. Run a prompt
|
||||
./target/debug/claw prompt "say hello"
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> **Windows (PowerShell):** the binary is `claw.exe`, not `claw`. Use `.\target\debug\claw.exe` or run `cargo run -- prompt "say hello"` to skip the path lookup.
|
||||
|
||||
> [!NOTE]
|
||||
> **Auth:** claw requires an **API key** (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.) — Claude subscription login is not a supported auth path.
|
||||
|
||||
Run the workspace test suite:
|
||||
|
||||
```bash
|
||||
|
||||
12
ROADMAP.md
12
ROADMAP.md
@@ -500,3 +500,15 @@ Model name prefix now wins unconditionally over env-var presence. Regression tes
|
||||
37. **Claude subscription login path should be removed, not deprecated** -- dogfooded 2026-04-09. Official auth should be API key only (ANTHROPIC_API_KEY or OAuth access token via ANTHROPIC_AUTH_TOKEN). claw login with Claude subscription credentials creates legal/billing ambiguity. Fix: remove the subscription login surface entirely; update README/USAGE.md to say API key is the only supported path. Source: gaebal-gajae policy decision 2026-04-09.
|
||||
|
||||
38. **Dead-session opacity: bot cannot self-detect compaction vs broken tool surface** -- dogfooded 2026-04-09. Jobdori session spent ~15h declaring itself "dead" in-channel while tools were actually returning correct results within each turn. Root cause: context compaction causes tool outputs to be summarised away between turns, making the bot interpret absence-of-remembered-output as tool failure. This is a distinct failure mode from ROADMAP #31 (executor quirks): the session is alive and tools are functional, but the agent cannot tell the difference between "my last tool call produced no output" (compaction) and "the tool is broken". Downstream: repetitive false-dead signals in the channel, work not getting done despite the execution surface being live. Fix shape: (a) probe with a short known-output command at turn start if context has been compacted; (b) gate "I am dead" declarations behind at least one within-turn tool call with a verified non-empty result; (c) consider adding a session-health canary cron that fires a wake with a minimal probe and checks the result. Source: Jobdori self-dogfood 2026-04-09; observed in #clawcode-building-in-public across multiple Clawhip nudge cycles.
|
||||
|
||||
39. **Several slash commands are registered but not implemented: /branch, /rewind, /ide, /tag, /output-style, /add-dir** -- dogfooded 2026-04-09. These commands appear in the REPL completions surface but silently print 'Command registered but not yet implemented.' and return false. Users (mezz2301 in #claw-code) hit this as 'many features are not supported in this version now'. Fix shape: either (a) implement the missing commands, or (b) remove them from completions/help output until they are ready, so the discovery surface matches what actually works. Source: mezz2301 in #claw-code 2026-04-09; pinpointed in main.rs:3728.
|
||||
|
||||
40. **Surface broken installed plugins before they become support ghosts** — community-support lane. Clawhip commit `ff6d3b7` on worktree `claw-code-community-support-plugin-list-load-failures` / branch `community-support/plugin-list-load-failures`. When an installed plugin has a broken manifest (missing hook scripts, parse errors, bad json), the plugin silently fails to load and the user sees nothing — no warning, no list entry, no hint. Related to ROADMAP #27 (host plugin path leaking into tests) but at the user-facing surface: the test gap and the UX gap are siblings of the same root. Landing on `main` will close the silent-ghost class of support issues where users report "my plugin does nothing" with no error to share. Track until merged to `main`.
|
||||
|
||||
40. **Surface broken installed plugins before they become support ghosts** — community-support lane. Clawhip commit `ff6d3b7` on worktree `claw-code-community-support-plugin-list-load-failures` / branch `community-support/plugin-list-load-failures`. When an installed plugin has a broken manifest (missing hook scripts, parse errors, bad json), the plugin silently fails to load and the user sees nothing — no warning, no list entry, no hint. Related to ROADMAP #27 (host plugin path leaking into tests) but at the user-facing surface: the test gap and the UX gap are siblings of the same root. Landing on `main` will close the silent-ghost class of support issues where users report "my plugin does nothing" with no error to share. Track until merged to `main`.
|
||||
|
||||
41. **Stop ambient plugin state from skewing CLI regression checks** — community-support lane. Clawhip commit `7d493a7` on worktree `claw-code-community-support-plugin-test-sealing` / branch `community-support/plugin-test-sealing`. Companion to #40: the test sealing gap is the CI/developer side of the same root — host `~/.claude/plugins/installed/` bleeds into CLI test runs, making regression checks non-deterministic on any machine with a non-pristine plugin install. Closely related to ROADMAP #27 (dev/rust `cargo test` reads host plugin state). Track until merged to `main`.
|
||||
|
||||
42. **`--output-format json` errors emitted as prose, not JSON** — dogfooded 2026-04-09. When `claw --output-format json prompt` hits an API error, the error was printed as plain text (`error: api returned 401 ...`) to stderr instead of a JSON object. Any tool or CI step parsing claw's JSON output gets nothing parseable on failure — the error is invisible to the consumer. **Fix (`a...`):** detect `--output-format json` in `main()` at process exit and emit `{"type":"error","error":"<message>"}` to stderr instead of the prose format. Non-JSON path unchanged. **Done** in this nudge cycle.
|
||||
|
||||
43. **Hook ingress opacity: typed hook-health/delivery report missing** — dogfooded 2026-04-09 while wiring the agentika timer→hook→session bridge. Debugging hook delivery required manual HTTP probing and inferring state from raw status codes (404 = no route, 405 = route exists, 400 = body missing required field). No typed endpoint exists to report: route present/absent, accepted methods, mapping matched/not matched, target session resolved/not resolved, last delivery failure class. Fix shape: add `GET /hooks/health` (or `/hooks/status`) returning a structured JSON diagnostic — no auth exposure, just routing/matching/session state. Source: gaebal-gajae dogfood 2026-04-09.
|
||||
|
||||
@@ -1938,6 +1938,42 @@ pub fn suggest_slash_commands(input: &str, limit: usize) -> Vec<String> {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Render the slash-command help section, optionally excluding stub commands
|
||||
/// (commands that are registered in the spec list but not yet implemented).
|
||||
/// Pass an empty slice to include all commands.
|
||||
pub fn render_slash_command_help_filtered(exclude: &[&str]) -> String {
|
||||
let mut lines = vec![
|
||||
"Slash commands".to_string(),
|
||||
" Start here /status, /diff, /agents, /skills, /commit".to_string(),
|
||||
" [resume] also works with --resume SESSION.jsonl".to_string(),
|
||||
String::new(),
|
||||
];
|
||||
|
||||
let categories = ["Session", "Tools", "Config", "Debug"];
|
||||
|
||||
for category in categories {
|
||||
lines.push(category.to_string());
|
||||
for spec in slash_command_specs()
|
||||
.iter()
|
||||
.filter(|spec| slash_command_category(spec.name) == category)
|
||||
.filter(|spec| !exclude.contains(&spec.name))
|
||||
{
|
||||
lines.push(format_slash_command_help_line(spec));
|
||||
}
|
||||
lines.push(String::new());
|
||||
}
|
||||
|
||||
lines
|
||||
.into_iter()
|
||||
.rev()
|
||||
.skip_while(String::is_empty)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.rev()
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
pub fn render_slash_command_help() -> String {
|
||||
let mut lines = vec![
|
||||
"Slash commands".to_string(),
|
||||
|
||||
@@ -504,6 +504,10 @@ where
|
||||
&self.session
|
||||
}
|
||||
|
||||
pub fn api_client_mut(&mut self) -> &mut C {
|
||||
&mut self.api_client
|
||||
}
|
||||
|
||||
pub fn session_mut(&mut self) -> &mut Session {
|
||||
&mut self.session
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ use commands::{
|
||||
classify_skills_slash_command, handle_agents_slash_command, handle_agents_slash_command_json,
|
||||
handle_mcp_slash_command, handle_mcp_slash_command_json, handle_plugins_slash_command,
|
||||
handle_skills_slash_command, handle_skills_slash_command_json, render_slash_command_help,
|
||||
resume_supported_slash_commands, slash_command_specs, validate_slash_command_input,
|
||||
SkillSlashDispatch, SlashCommand,
|
||||
render_slash_command_help_filtered, resolve_skill_invocation, resume_supported_slash_commands,
|
||||
slash_command_specs, validate_slash_command_input, SkillSlashDispatch, SlashCommand,
|
||||
};
|
||||
use compat_harness::{extract_manifest, UpstreamPaths};
|
||||
use init::initialize_repo;
|
||||
@@ -110,7 +110,22 @@ type RuntimePluginStateBuildOutput = (
|
||||
fn main() {
|
||||
if let Err(error) = run() {
|
||||
let message = error.to_string();
|
||||
if message.contains("`claw --help`") {
|
||||
// When --output-format json is active, emit errors as JSON so downstream
|
||||
// tools can parse failures the same way they parse successes (ROADMAP #42).
|
||||
let argv: Vec<String> = std::env::args().collect();
|
||||
let json_output = argv
|
||||
.windows(2)
|
||||
.any(|w| w[0] == "--output-format" && w[1] == "json")
|
||||
|| argv.iter().any(|a| a == "--output-format=json");
|
||||
if json_output {
|
||||
eprintln!(
|
||||
"{}",
|
||||
serde_json::json!({
|
||||
"type": "error",
|
||||
"error": message,
|
||||
})
|
||||
);
|
||||
} else if message.contains("`claw --help`") {
|
||||
eprintln!("error: {message}");
|
||||
} else {
|
||||
eprintln!(
|
||||
@@ -209,7 +224,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
permission_mode,
|
||||
compact,
|
||||
base_commit,
|
||||
..
|
||||
reasoning_effort,
|
||||
} => {
|
||||
run_stale_base_preflight(base_commit.as_deref());
|
||||
// Only consume piped stdin as prompt context when the permission
|
||||
@@ -223,11 +238,9 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
None
|
||||
};
|
||||
let effective_prompt = merge_prompt_with_stdin(&prompt, stdin_context.as_deref());
|
||||
LiveCli::new(model, true, allowed_tools, permission_mode)?.run_turn_with_output(
|
||||
&effective_prompt,
|
||||
output_format,
|
||||
compact,
|
||||
)?;
|
||||
let mut cli = LiveCli::new(model, true, allowed_tools, permission_mode)?;
|
||||
cli.set_reasoning_effort(reasoning_effort);
|
||||
cli.run_turn_with_output(&effective_prompt, output_format, compact)?;
|
||||
}
|
||||
CliAction::Login { output_format } => run_login(output_format)?,
|
||||
CliAction::Logout { output_format } => run_logout(output_format)?,
|
||||
@@ -244,8 +257,14 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
allowed_tools,
|
||||
permission_mode,
|
||||
base_commit,
|
||||
..
|
||||
} => run_repl(model, allowed_tools, permission_mode, base_commit)?,
|
||||
reasoning_effort,
|
||||
} => run_repl(
|
||||
model,
|
||||
allowed_tools,
|
||||
permission_mode,
|
||||
base_commit,
|
||||
reasoning_effort,
|
||||
)?,
|
||||
CliAction::HelpTopic(topic) => print_help_topic(topic),
|
||||
CliAction::Help { output_format } => print_help(output_format)?,
|
||||
}
|
||||
@@ -377,7 +396,8 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
let mut allowed_tool_values = Vec::new();
|
||||
let mut compact = false;
|
||||
let mut base_commit: Option<String> = None;
|
||||
let mut rest = Vec::new();
|
||||
let mut reasoning_effort: Option<String> = None;
|
||||
let mut rest: Vec<String> = Vec::new();
|
||||
let mut index = 0;
|
||||
|
||||
while index < args.len() {
|
||||
@@ -386,6 +406,31 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
wants_help = true;
|
||||
index += 1;
|
||||
}
|
||||
"--help" | "-h"
|
||||
if !rest.is_empty()
|
||||
&& matches!(
|
||||
rest[0].as_str(),
|
||||
"prompt"
|
||||
| "login"
|
||||
| "logout"
|
||||
| "version"
|
||||
| "state"
|
||||
| "init"
|
||||
| "export"
|
||||
| "commit"
|
||||
| "pr"
|
||||
| "issue"
|
||||
) =>
|
||||
{
|
||||
// `--help` following a subcommand that would otherwise forward
|
||||
// the arg to the API (e.g. `claw prompt --help`) should show
|
||||
// top-level help instead. Subcommands that consume their own
|
||||
// args (agents, mcp, plugins, skills) and local help-topic
|
||||
// subcommands (status, sandbox, doctor) must NOT be intercepted
|
||||
// here — they handle --help in their own dispatch paths.
|
||||
wants_help = true;
|
||||
index += 1;
|
||||
}
|
||||
"--version" | "-V" => {
|
||||
wants_version = true;
|
||||
index += 1;
|
||||
@@ -442,6 +487,28 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
base_commit = Some(flag[14..].to_string());
|
||||
index += 1;
|
||||
}
|
||||
"--reasoning-effort" => {
|
||||
let value = args
|
||||
.get(index + 1)
|
||||
.ok_or_else(|| "missing value for --reasoning-effort".to_string())?;
|
||||
if !matches!(value.as_str(), "low" | "medium" | "high") {
|
||||
return Err(format!(
|
||||
"invalid value for --reasoning-effort: '{value}'; must be low, medium, or high"
|
||||
));
|
||||
}
|
||||
reasoning_effort = Some(value.clone());
|
||||
index += 2;
|
||||
}
|
||||
flag if flag.starts_with("--reasoning-effort=") => {
|
||||
let value = &flag[19..];
|
||||
if !matches!(value, "low" | "medium" | "high") {
|
||||
return Err(format!(
|
||||
"invalid value for --reasoning-effort: '{value}'; must be low, medium, or high"
|
||||
));
|
||||
}
|
||||
reasoning_effort = Some(value.to_string());
|
||||
index += 1;
|
||||
}
|
||||
"-p" => {
|
||||
// Claw Code compat: -p "prompt" = one-shot prompt
|
||||
let prompt = args[index + 1..].join(" ");
|
||||
@@ -457,7 +524,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
.unwrap_or_else(default_permission_mode),
|
||||
compact,
|
||||
base_commit: base_commit.clone(),
|
||||
reasoning_effort: None,
|
||||
reasoning_effort: reasoning_effort.clone(),
|
||||
});
|
||||
}
|
||||
"--print" => {
|
||||
@@ -516,7 +583,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
allowed_tools,
|
||||
permission_mode,
|
||||
base_commit,
|
||||
reasoning_effort: None,
|
||||
reasoning_effort: reasoning_effort.clone(),
|
||||
});
|
||||
}
|
||||
if rest.first().map(String::as_str) == Some("--resume") {
|
||||
@@ -555,7 +622,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
permission_mode,
|
||||
compact,
|
||||
base_commit,
|
||||
reasoning_effort: None,
|
||||
reasoning_effort: reasoning_effort.clone(),
|
||||
}),
|
||||
SkillSlashDispatch::Local => Ok(CliAction::Skills {
|
||||
args,
|
||||
@@ -581,7 +648,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
permission_mode,
|
||||
compact,
|
||||
base_commit: base_commit.clone(),
|
||||
reasoning_effort: None,
|
||||
reasoning_effort: reasoning_effort.clone(),
|
||||
})
|
||||
}
|
||||
other if other.starts_with('/') => parse_direct_slash_cli_action(
|
||||
@@ -592,6 +659,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
permission_mode,
|
||||
compact,
|
||||
base_commit,
|
||||
reasoning_effort,
|
||||
),
|
||||
_other => Ok(CliAction::Prompt {
|
||||
prompt: rest.join(" "),
|
||||
@@ -601,7 +669,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
permission_mode,
|
||||
compact,
|
||||
base_commit,
|
||||
reasoning_effort: None,
|
||||
reasoning_effort: reasoning_effort.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -695,6 +763,7 @@ fn parse_direct_slash_cli_action(
|
||||
permission_mode: PermissionMode,
|
||||
compact: bool,
|
||||
base_commit: Option<String>,
|
||||
reasoning_effort: Option<String>,
|
||||
) -> Result<CliAction, String> {
|
||||
let raw = rest.join(" ");
|
||||
match SlashCommand::parse(&raw) {
|
||||
@@ -722,7 +791,7 @@ fn parse_direct_slash_cli_action(
|
||||
permission_mode,
|
||||
compact,
|
||||
base_commit,
|
||||
reasoning_effort: None,
|
||||
reasoning_effort: reasoning_effort.clone(),
|
||||
}),
|
||||
SkillSlashDispatch::Local => Ok(CliAction::Skills {
|
||||
args,
|
||||
@@ -1358,16 +1427,16 @@ fn run_worker_state(output_format: CliOutputFormat) -> Result<(), Box<dyn std::e
|
||||
let cwd = env::current_dir()?;
|
||||
let state_path = cwd.join(".claw").join("worker-state.json");
|
||||
if !state_path.exists() {
|
||||
match output_format {
|
||||
CliOutputFormat::Text => {
|
||||
println!("No worker state file found at {}", state_path.display())
|
||||
}
|
||||
CliOutputFormat::Json => println!(
|
||||
"{}",
|
||||
serde_json::json!({"error": "no_state_file", "path": state_path.display().to_string()})
|
||||
),
|
||||
}
|
||||
return Ok(());
|
||||
// Emit a structured error, then return Err so the process exits 1.
|
||||
// Callers (scripts, CI) need a non-zero exit to detect "no state" without
|
||||
// parsing prose output.
|
||||
// Let the error propagate to main() which will format it correctly
|
||||
// (prose for text mode, JSON envelope for --output-format json).
|
||||
return Err(format!(
|
||||
"no worker state file found at {} — run a worker first",
|
||||
state_path.display()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
let raw = std::fs::read_to_string(&state_path)?;
|
||||
match output_format {
|
||||
@@ -2772,10 +2841,12 @@ fn run_repl(
|
||||
allowed_tools: Option<AllowedToolSet>,
|
||||
permission_mode: PermissionMode,
|
||||
base_commit: Option<String>,
|
||||
reasoning_effort: Option<String>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_stale_base_preflight(base_commit.as_deref());
|
||||
let resolved_model = resolve_repl_model(model);
|
||||
let mut cli = LiveCli::new(resolved_model, true, allowed_tools, permission_mode)?;
|
||||
cli.set_reasoning_effort(reasoning_effort);
|
||||
let mut editor =
|
||||
input::LineEditor::new("> ", cli.repl_completion_candidates().unwrap_or_default());
|
||||
println!("{}", cli.startup_banner());
|
||||
@@ -2806,6 +2877,26 @@ fn run_repl(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Bare-word skill dispatch: if the first token of the input
|
||||
// matches a known skill name, invoke it as `/skills <input>`
|
||||
// rather than forwarding raw text to the LLM (ROADMAP #36).
|
||||
let bare_first_token = trimmed.split_whitespace().next().unwrap_or_default();
|
||||
let looks_like_skill_name = !bare_first_token.is_empty()
|
||||
&& !bare_first_token.starts_with('/')
|
||||
&& bare_first_token
|
||||
.chars()
|
||||
.all(|c| c.is_alphanumeric() || c == '-' || c == '_');
|
||||
if looks_like_skill_name {
|
||||
let cwd = std::env::current_dir().unwrap_or_default();
|
||||
if let Ok(SkillSlashDispatch::Invoke(prompt)) =
|
||||
resolve_skill_invocation(&cwd, Some(&trimmed))
|
||||
{
|
||||
editor.push_history(input);
|
||||
cli.record_prompt_history(&trimmed);
|
||||
cli.run_turn(&prompt)?;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
editor.push_history(input);
|
||||
cli.record_prompt_history(&trimmed);
|
||||
cli.run_turn(&trimmed)?;
|
||||
@@ -3358,6 +3449,12 @@ impl LiveCli {
|
||||
Ok(cli)
|
||||
}
|
||||
|
||||
fn set_reasoning_effort(&mut self, effort: Option<String>) {
|
||||
if let Some(rt) = self.runtime.runtime.as_mut() {
|
||||
rt.api_client_mut().set_reasoning_effort(effort);
|
||||
}
|
||||
}
|
||||
|
||||
fn startup_banner(&self) -> String {
|
||||
let cwd = env::current_dir().map_or_else(
|
||||
|_| "<unknown>".to_string(),
|
||||
@@ -4660,7 +4757,7 @@ fn render_repl_help() -> String {
|
||||
" Browse sessions /session list".to_string(),
|
||||
" Show prompt history /history [count]".to_string(),
|
||||
String::new(),
|
||||
render_slash_command_help(),
|
||||
render_slash_command_help_filtered(STUB_COMMANDS),
|
||||
]
|
||||
.join(
|
||||
"
|
||||
@@ -6380,6 +6477,7 @@ struct AnthropicRuntimeClient {
|
||||
allowed_tools: Option<AllowedToolSet>,
|
||||
tool_registry: GlobalToolRegistry,
|
||||
progress_reporter: Option<InternalPromptProgressReporter>,
|
||||
reasoning_effort: Option<String>,
|
||||
}
|
||||
|
||||
impl AnthropicRuntimeClient {
|
||||
@@ -6444,8 +6542,13 @@ impl AnthropicRuntimeClient {
|
||||
allowed_tools,
|
||||
tool_registry,
|
||||
progress_reporter,
|
||||
reasoning_effort: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn set_reasoning_effort(&mut self, effort: Option<String>) {
|
||||
self.reasoning_effort = effort;
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_cli_auth_source() -> Result<AuthSource, Box<dyn std::error::Error>> {
|
||||
@@ -6491,6 +6594,7 @@ impl ApiClient for AnthropicRuntimeClient {
|
||||
.then(|| filter_tool_specs(&self.tool_registry, self.allowed_tools.as_ref())),
|
||||
tool_choice: self.enable_tools.then_some(ToolChoice::Auto),
|
||||
stream: true,
|
||||
reasoning_effort: self.reasoning_effort.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -6867,6 +6971,51 @@ fn collect_prompt_cache_events(summary: &runtime::TurnSummary) -> Vec<serde_json
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Slash commands that are registered in the spec list but not yet implemented
|
||||
/// in this build. Used to filter both REPL completions and help output so the
|
||||
/// discovery surface only shows commands that actually work (ROADMAP #39).
|
||||
const STUB_COMMANDS: &[&str] = &[
|
||||
"login",
|
||||
"logout",
|
||||
"vim",
|
||||
"upgrade",
|
||||
"stats",
|
||||
"share",
|
||||
"feedback",
|
||||
"files",
|
||||
"fast",
|
||||
"exit",
|
||||
"summary",
|
||||
"desktop",
|
||||
"brief",
|
||||
"advisor",
|
||||
"stickers",
|
||||
"insights",
|
||||
"thinkback",
|
||||
"release-notes",
|
||||
"security-review",
|
||||
"keybindings",
|
||||
"privacy-settings",
|
||||
"plan",
|
||||
"review",
|
||||
"tasks",
|
||||
"theme",
|
||||
"voice",
|
||||
"usage",
|
||||
"rename",
|
||||
"copy",
|
||||
"hooks",
|
||||
"context",
|
||||
"color",
|
||||
"effort",
|
||||
"branch",
|
||||
"rewind",
|
||||
"ide",
|
||||
"tag",
|
||||
"output-style",
|
||||
"add-dir",
|
||||
];
|
||||
|
||||
fn slash_command_completion_candidates_with_sessions(
|
||||
model: &str,
|
||||
active_session_id: Option<&str>,
|
||||
@@ -6875,9 +7024,14 @@ fn slash_command_completion_candidates_with_sessions(
|
||||
let mut completions = BTreeSet::new();
|
||||
|
||||
for spec in slash_command_specs() {
|
||||
if STUB_COMMANDS.contains(&spec.name) {
|
||||
continue;
|
||||
}
|
||||
completions.insert(format!("/{}", spec.name));
|
||||
for alias in spec.aliases {
|
||||
completions.insert(format!("/{alias}"));
|
||||
if !STUB_COMMANDS.contains(alias) {
|
||||
completions.insert(format!("/{alias}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7766,7 +7920,7 @@ fn print_help_to(out: &mut impl Write) -> io::Result<()> {
|
||||
)?;
|
||||
writeln!(out)?;
|
||||
writeln!(out, "Interactive slash commands:")?;
|
||||
writeln!(out, "{}", render_slash_command_help())?;
|
||||
writeln!(out, "{}", render_slash_command_help_filtered(STUB_COMMANDS))?;
|
||||
writeln!(out)?;
|
||||
let resume_commands = resume_supported_slash_commands()
|
||||
.into_iter()
|
||||
@@ -7860,7 +8014,7 @@ mod tests {
|
||||
summarize_tool_payload_for_markdown, validate_no_args, write_mcp_server_fixture, CliAction,
|
||||
CliOutputFormat, CliToolExecutor, GitWorkspaceSummary, InternalPromptProgressEvent,
|
||||
InternalPromptProgressState, LiveCli, LocalHelpTopic, PromptHistoryEntry, SlashCommand,
|
||||
StatusUsage, DEFAULT_MODEL, LATEST_SESSION_REFERENCE,
|
||||
StatusUsage, DEFAULT_MODEL, LATEST_SESSION_REFERENCE, STUB_COMMANDS,
|
||||
};
|
||||
use api::{ApiError, MessageResponse, OutputContentBlock, Usage};
|
||||
use plugins::{
|
||||
@@ -11006,6 +11160,58 @@ UU conflicted.rs",
|
||||
let _ = fs::remove_dir_all(source_root);
|
||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_reasoning_effort_value() {
|
||||
let err = parse_args(&[
|
||||
"--reasoning-effort".to_string(),
|
||||
"turbo".to_string(),
|
||||
"prompt".to_string(),
|
||||
"hello".to_string(),
|
||||
])
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
err.contains("invalid value for --reasoning-effort"),
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
assert!(err.contains("turbo"), "unexpected error: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_valid_reasoning_effort_values() {
|
||||
for value in ["low", "medium", "high"] {
|
||||
let result = parse_args(&[
|
||||
"--reasoning-effort".to_string(),
|
||||
value.to_string(),
|
||||
"prompt".to_string(),
|
||||
"hello".to_string(),
|
||||
]);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"--reasoning-effort {value} should be accepted, got: {:?}",
|
||||
result
|
||||
);
|
||||
if let Ok(CliAction::Prompt {
|
||||
reasoning_effort, ..
|
||||
}) = result
|
||||
{
|
||||
assert_eq!(reasoning_effort.as_deref(), Some(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stub_commands_absent_from_repl_completions() {
|
||||
let candidates =
|
||||
slash_command_completion_candidates_with_sessions("claude-3-5-sonnet", None, vec![]);
|
||||
for stub in STUB_COMMANDS {
|
||||
let with_slash = format!("/{stub}");
|
||||
assert!(
|
||||
!candidates.contains(&with_slash),
|
||||
"stub command {with_slash} should not appear in REPL completions"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_mcp_server_fixture(script_path: &Path) {
|
||||
|
||||
Reference in New Issue
Block a user