mirror of
https://github.com/instructkr/claude-code.git
synced 2026-06-03 02:56:44 +00:00
Compare commits
7 Commits
fix-160-se
...
fix-683-un
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60108dfbf6 | ||
|
|
bd9102f851 | ||
|
|
e7d5d08892 | ||
|
|
f003a108e3 | ||
|
|
a76dda2b19 | ||
|
|
013694476e | ||
|
|
3d02baf567 |
@@ -640,14 +640,7 @@ pub fn model_token_limit(model: &str) -> Option<ModelTokenLimit> {
|
|||||||
max_output_tokens: 16_384,
|
max_output_tokens: 16_384,
|
||||||
context_window_tokens: 256_000,
|
context_window_tokens: 256_000,
|
||||||
}),
|
}),
|
||||||
// Hotfix: Unknown models get conservative defaults to avoid crashes.
|
_ => None,
|
||||||
// Uses the minimum of known supported models: max_output_tokens: 16_384,
|
|
||||||
// context_window_tokens: 131_072. This may under-utilize the model's
|
|
||||||
// actual capabilities but hopefully ensures safer operation.
|
|
||||||
_ => Some(ModelTokenLimit {
|
|
||||||
max_output_tokens: 16_384,
|
|
||||||
context_window_tokens: 131_072,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1857,6 +1857,7 @@ mod tests {
|
|||||||
delta: super::ChunkDelta {
|
delta: super::ChunkDelta {
|
||||||
content: None,
|
content: None,
|
||||||
reasoning_content: Some("think".to_string()),
|
reasoning_content: Some("think".to_string()),
|
||||||
|
thinking: None,
|
||||||
tool_calls: Vec::new(),
|
tool_calls: Vec::new(),
|
||||||
},
|
},
|
||||||
finish_reason: None,
|
finish_reason: None,
|
||||||
@@ -1873,6 +1874,7 @@ mod tests {
|
|||||||
delta: super::ChunkDelta {
|
delta: super::ChunkDelta {
|
||||||
content: Some(" answer".to_string()),
|
content: Some(" answer".to_string()),
|
||||||
reasoning_content: None,
|
reasoning_content: None,
|
||||||
|
thinking: None,
|
||||||
tool_calls: Vec::new(),
|
tool_calls: Vec::new(),
|
||||||
},
|
},
|
||||||
finish_reason: Some("stop".to_string()),
|
finish_reason: Some("stop".to_string()),
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ async fn send_message_posts_json_and_parses_response() {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
request.headers.get("user-agent").map(String::as_str),
|
request.headers.get("user-agent").map(String::as_str),
|
||||||
Some("claude-code/0.1.0")
|
Some("claude-code/0.1.3")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
request.headers.get("anthropic-beta").map(String::as_str),
|
request.headers.get("anthropic-beta").map(String::as_str),
|
||||||
|
|||||||
@@ -1180,6 +1180,9 @@ pub enum SlashCommand {
|
|||||||
count: Option<String>,
|
count: Option<String>,
|
||||||
},
|
},
|
||||||
Unknown(String),
|
Unknown(String),
|
||||||
|
Team {
|
||||||
|
action: Option<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -1277,6 +1280,7 @@ impl SlashCommand {
|
|||||||
Self::Tag { .. } => "/tag",
|
Self::Tag { .. } => "/tag",
|
||||||
Self::OutputStyle { .. } => "/output-style",
|
Self::OutputStyle { .. } => "/output-style",
|
||||||
Self::AddDir { .. } => "/add-dir",
|
Self::AddDir { .. } => "/add-dir",
|
||||||
|
Self::Team { .. } => "/team",
|
||||||
Self::Sandbox => "/sandbox",
|
Self::Sandbox => "/sandbox",
|
||||||
Self::Mcp { .. } => "/mcp",
|
Self::Mcp { .. } => "/mcp",
|
||||||
Self::Export { .. } => "/export",
|
Self::Export { .. } => "/export",
|
||||||
@@ -4312,6 +4316,7 @@ pub fn handle_slash_command(
|
|||||||
| SlashCommand::OutputStyle { .. }
|
| SlashCommand::OutputStyle { .. }
|
||||||
| SlashCommand::AddDir { .. }
|
| SlashCommand::AddDir { .. }
|
||||||
| SlashCommand::History { .. }
|
| SlashCommand::History { .. }
|
||||||
|
| SlashCommand::Team { .. }
|
||||||
| SlashCommand::Unknown(_) => None,
|
| SlashCommand::Unknown(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1230,4 +1230,44 @@ mod tests {
|
|||||||
);
|
);
|
||||||
fs::remove_dir_all(base).expect("temp dir should clean up");
|
fs::remove_dir_all(base).expect("temp dir should clean up");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// #160 regression: store-level list_sessions/session_exists/delete_session
|
||||||
|
/// lifecycle works end-to-end.
|
||||||
|
#[test]
|
||||||
|
fn session_store_lifecycle_regression_160() {
|
||||||
|
// given
|
||||||
|
let base = temp_dir();
|
||||||
|
fs::create_dir_all(&base).expect("base dir should exist");
|
||||||
|
let store = SessionStore::from_cwd(&base).expect("store should build");
|
||||||
|
let session = persist_session_via_store(&store, "160 regression test");
|
||||||
|
|
||||||
|
// when/then — session exists and is listed before deletion
|
||||||
|
assert!(
|
||||||
|
!store.list_sessions().expect("list").is_empty(),
|
||||||
|
"store should have at least one session"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
store.session_exists(&session.session_id),
|
||||||
|
"session should exist before deletion"
|
||||||
|
);
|
||||||
|
|
||||||
|
// when — delete the session
|
||||||
|
let deleted = store
|
||||||
|
.delete_session(&session.session_id)
|
||||||
|
.expect("delete should succeed");
|
||||||
|
|
||||||
|
// then — session is gone
|
||||||
|
assert_eq!(deleted.id, session.session_id);
|
||||||
|
assert!(!deleted.path.exists(), "session file should be removed");
|
||||||
|
assert!(
|
||||||
|
!store.session_exists(&session.session_id),
|
||||||
|
"session should not exist after deletion"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
store.list_sessions().expect("list").is_empty(),
|
||||||
|
"store should have no sessions after deletion"
|
||||||
|
);
|
||||||
|
|
||||||
|
fs::remove_dir_all(base).expect("temp dir should clean up");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,6 +274,8 @@ fn classify_error_kind(message: &str) -> &'static str {
|
|||||||
"no_managed_sessions"
|
"no_managed_sessions"
|
||||||
} else if message.contains("unsupported ACP invocation") {
|
} else if message.contains("unsupported ACP invocation") {
|
||||||
"unsupported_acp_invocation"
|
"unsupported_acp_invocation"
|
||||||
|
} else if message.contains("unsupported skills action") {
|
||||||
|
"unsupported_skills_action"
|
||||||
} else if message.contains("unrecognized argument") || message.contains("unknown option") {
|
} else if message.contains("unrecognized argument") || message.contains("unknown option") {
|
||||||
"cli_parse"
|
"cli_parse"
|
||||||
} else if message.contains("invalid model syntax") {
|
} else if message.contains("invalid model syntax") {
|
||||||
@@ -877,6 +879,35 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if wants_help {
|
if wants_help {
|
||||||
|
// #684: --help before subcommand should still route to subcommand-specific
|
||||||
|
// help when the subcommand is one of the local-help-topic commands.
|
||||||
|
if let Some(action) = parse_local_help_action(&rest, output_format) {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
// When --help was consumed before the subcommand, rest has no help flag.
|
||||||
|
// If rest is a simple local-help subcommand with no extra args, route there.
|
||||||
|
if !rest.is_empty() && rest[1..].iter().all(|a| is_help_flag(a)) {
|
||||||
|
let topic = match rest[0].as_str() {
|
||||||
|
"status" => Some(LocalHelpTopic::Status),
|
||||||
|
"sandbox" => Some(LocalHelpTopic::Sandbox),
|
||||||
|
"doctor" => Some(LocalHelpTopic::Doctor),
|
||||||
|
"acp" => Some(LocalHelpTopic::Acp),
|
||||||
|
"init" => Some(LocalHelpTopic::Init),
|
||||||
|
"state" => Some(LocalHelpTopic::State),
|
||||||
|
"export" => Some(LocalHelpTopic::Export),
|
||||||
|
"version" => Some(LocalHelpTopic::Version),
|
||||||
|
"system-prompt" => Some(LocalHelpTopic::SystemPrompt),
|
||||||
|
"dump-manifests" => Some(LocalHelpTopic::DumpManifests),
|
||||||
|
"bootstrap-plan" => Some(LocalHelpTopic::BootstrapPlan),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(topic) = topic {
|
||||||
|
return Ok(CliAction::HelpTopic {
|
||||||
|
topic,
|
||||||
|
output_format,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
return Ok(CliAction::Help { output_format });
|
return Ok(CliAction::Help { output_format });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1020,6 +1051,14 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
),
|
),
|
||||||
"skills" => {
|
"skills" => {
|
||||||
let args = join_optional_args(&rest[1..]);
|
let args = join_optional_args(&rest[1..]);
|
||||||
|
if let Some(action) = args.as_deref() {
|
||||||
|
let first_word = action.split_whitespace().next().unwrap_or(action);
|
||||||
|
if matches!(first_word, "remove" | "add" | "uninstall" | "delete") {
|
||||||
|
return Err(format!(
|
||||||
|
"unsupported skills action: {first_word}. Supported actions: list, install <path>, help, or <skill> [args]"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
match classify_skills_slash_command(args.as_deref()) {
|
match classify_skills_slash_command(args.as_deref()) {
|
||||||
SkillSlashDispatch::Invoke(prompt) => Ok(CliAction::Prompt {
|
SkillSlashDispatch::Invoke(prompt) => Ok(CliAction::Prompt {
|
||||||
prompt,
|
prompt,
|
||||||
@@ -1117,7 +1156,10 @@ fn parse_local_help_action(
|
|||||||
rest: &[String],
|
rest: &[String],
|
||||||
output_format: CliOutputFormat,
|
output_format: CliOutputFormat,
|
||||||
) -> Option<Result<CliAction, String>> {
|
) -> Option<Result<CliAction, String>> {
|
||||||
if rest.len() != 2 || !is_help_flag(&rest[1]) {
|
if rest.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if !rest.iter().any(|a| is_help_flag(a)) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1126,10 +1168,6 @@ fn parse_local_help_action(
|
|||||||
"sandbox" => LocalHelpTopic::Sandbox,
|
"sandbox" => LocalHelpTopic::Sandbox,
|
||||||
"doctor" => LocalHelpTopic::Doctor,
|
"doctor" => LocalHelpTopic::Doctor,
|
||||||
"acp" => LocalHelpTopic::Acp,
|
"acp" => LocalHelpTopic::Acp,
|
||||||
// #141: add the subcommands that were previously falling back
|
|
||||||
// to global help (init/state/export/version) or erroring out
|
|
||||||
// (system-prompt/dump-manifests) or printing their primary
|
|
||||||
// output instead of help text (bootstrap-plan).
|
|
||||||
"init" => LocalHelpTopic::Init,
|
"init" => LocalHelpTopic::Init,
|
||||||
"state" => LocalHelpTopic::State,
|
"state" => LocalHelpTopic::State,
|
||||||
"export" => LocalHelpTopic::Export,
|
"export" => LocalHelpTopic::Export,
|
||||||
@@ -1139,6 +1177,10 @@ fn parse_local_help_action(
|
|||||||
"bootstrap-plan" => LocalHelpTopic::BootstrapPlan,
|
"bootstrap-plan" => LocalHelpTopic::BootstrapPlan,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
let has_non_help = rest[1..].iter().any(|a| !is_help_flag(a));
|
||||||
|
if has_non_help {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
Some(Ok(CliAction::HelpTopic {
|
Some(Ok(CliAction::HelpTopic {
|
||||||
topic,
|
topic,
|
||||||
output_format,
|
output_format,
|
||||||
@@ -1172,8 +1214,9 @@ fn parse_single_word_command_alias(
|
|||||||
|
|
||||||
if is_diagnostic && rest.len() > 1 {
|
if is_diagnostic && rest.len() > 1 {
|
||||||
// Diagnostic verb with trailing args: reject unrecognized suffix
|
// Diagnostic verb with trailing args: reject unrecognized suffix
|
||||||
if is_help_flag(&rest[1]) && rest.len() == 2 {
|
let all_extra_are_help = rest[1..].iter().all(|a| is_help_flag(a));
|
||||||
// "doctor --help" is valid, routed to parse_local_help_action() instead
|
if all_extra_are_help {
|
||||||
|
// "doctor --help -h" is valid, routed to parse_local_help_action() instead
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// Unrecognized suffix like "--json"
|
// Unrecognized suffix like "--json"
|
||||||
@@ -4209,7 +4252,8 @@ fn run_resume_command(
|
|||||||
| SlashCommand::Ide { .. }
|
| SlashCommand::Ide { .. }
|
||||||
| SlashCommand::Tag { .. }
|
| SlashCommand::Tag { .. }
|
||||||
| SlashCommand::OutputStyle { .. }
|
| SlashCommand::OutputStyle { .. }
|
||||||
| SlashCommand::AddDir { .. } => Err("unsupported resumed slash command".into()),
|
| SlashCommand::AddDir { .. }
|
||||||
|
| SlashCommand::Team { .. } => Err("unsupported resumed slash command".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5456,7 +5500,8 @@ impl LiveCli {
|
|||||||
| SlashCommand::Ide { .. }
|
| SlashCommand::Ide { .. }
|
||||||
| SlashCommand::Tag { .. }
|
| SlashCommand::Tag { .. }
|
||||||
| SlashCommand::OutputStyle { .. }
|
| SlashCommand::OutputStyle { .. }
|
||||||
| SlashCommand::AddDir { .. } => {
|
| SlashCommand::AddDir { .. }
|
||||||
|
| SlashCommand::Team { .. } => {
|
||||||
let cmd_name = command.slash_name();
|
let cmd_name = command.slash_name();
|
||||||
eprintln!("{cmd_name} is not yet implemented in this build.");
|
eprintln!("{cmd_name} is not yet implemented in this build.");
|
||||||
false
|
false
|
||||||
@@ -12711,6 +12756,23 @@ mod tests {
|
|||||||
assert!(error.contains("skills"));
|
assert!(error.contains("skills"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unsupported_skills_actions_return_typed_error_683() {
|
||||||
|
for action in ["remove", "add", "uninstall", "delete"] {
|
||||||
|
let error = parse_args(&["skills".to_string(), action.to_string()])
|
||||||
|
.expect_err(&format!("skills {action} should error"));
|
||||||
|
assert!(
|
||||||
|
error.contains("unsupported skills action"),
|
||||||
|
"skills {action} should contain 'unsupported skills action', got: {error}"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
classify_error_kind(&error),
|
||||||
|
"unsupported_skills_action",
|
||||||
|
"skills {action} should classify as unsupported_skills_action, got: {error}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn typoed_status_subcommand_returns_did_you_mean_error() {
|
fn typoed_status_subcommand_returns_did_you_mean_error() {
|
||||||
let error = parse_args(&["statuss".to_string()]).expect_err("statuss should error");
|
let error = parse_args(&["statuss".to_string()]).expect_err("statuss should error");
|
||||||
|
|||||||
Reference in New Issue
Block a user