mirror of
https://github.com/ultraworkers/claw-code.git
synced 2026-05-30 20:04:17 +08:00
omx(team): merge worker-4
This commit is contained in:
commit
8fd5894022
@ -225,7 +225,7 @@ const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
|
|||||||
argument_hint: Some(
|
argument_hint: Some(
|
||||||
"[list|exists <session-id>|switch <session-id>|fork [branch-name]|delete <session-id> [--force]]",
|
"[list|exists <session-id>|switch <session-id>|fork [branch-name]|delete <session-id> [--force]]",
|
||||||
),
|
),
|
||||||
resume_supported: false,
|
resume_supported: true,
|
||||||
},
|
},
|
||||||
SlashCommandSpec {
|
SlashCommandSpec {
|
||||||
name: "plugin",
|
name: "plugin",
|
||||||
@ -4596,16 +4596,16 @@ mod tests {
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
SlashCommand::parse("/session exists abc123"),
|
SlashCommand::parse("/session switch abc123"),
|
||||||
Ok(Some(SlashCommand::Session {
|
Ok(Some(SlashCommand::Session {
|
||||||
action: Some("exists".to_string()),
|
action: Some("switch".to_string()),
|
||||||
target: Some("abc123".to_string())
|
target: Some("abc123".to_string())
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
SlashCommand::parse("/session switch abc123"),
|
SlashCommand::parse("/session exists abc123"),
|
||||||
Ok(Some(SlashCommand::Session {
|
Ok(Some(SlashCommand::Session {
|
||||||
action: Some("switch".to_string()),
|
action: Some("exists".to_string()),
|
||||||
target: Some("abc123".to_string())
|
target: Some("abc123".to_string())
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4157,7 +4157,6 @@ fn run_resume_command(
|
|||||||
| SlashCommand::Resume { .. }
|
| SlashCommand::Resume { .. }
|
||||||
| SlashCommand::Model { .. }
|
| SlashCommand::Model { .. }
|
||||||
| SlashCommand::Permissions { .. }
|
| SlashCommand::Permissions { .. }
|
||||||
| SlashCommand::Session { .. }
|
|
||||||
| SlashCommand::Login
|
| SlashCommand::Login
|
||||||
| SlashCommand::Logout
|
| SlashCommand::Logout
|
||||||
| SlashCommand::Vim
|
| SlashCommand::Vim
|
||||||
@ -6102,6 +6101,140 @@ fn confirm_session_deletion(session_id: &str) -> bool {
|
|||||||
matches!(answer.trim(), "y" | "Y" | "yes" | "Yes" | "YES")
|
matches!(answer.trim(), "y" | "Y" | "yes" | "Yes" | "YES")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn session_details_json(sessions: &[ManagedSessionSummary]) -> Vec<serde_json::Value> {
|
||||||
|
sessions
|
||||||
|
.iter()
|
||||||
|
.map(|session| {
|
||||||
|
serde_json::json!({
|
||||||
|
"id": session.id,
|
||||||
|
"path": session.path.display().to_string(),
|
||||||
|
"message_count": session.message_count,
|
||||||
|
"updated_at_ms": session.updated_at_ms,
|
||||||
|
"modified_epoch_millis": session.modified_epoch_millis,
|
||||||
|
"parent_session_id": session.parent_session_id,
|
||||||
|
"branch_name": session.branch_name,
|
||||||
|
"lifecycle": session.lifecycle.json_value(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn session_exists_json(
|
||||||
|
target: &str,
|
||||||
|
active_session_id: &str,
|
||||||
|
) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
|
||||||
|
let handle = create_managed_session_handle(target)?;
|
||||||
|
let resolved = resolve_session_reference(target).ok();
|
||||||
|
let exists = resolved.is_some();
|
||||||
|
let resolved_id = resolved
|
||||||
|
.as_ref()
|
||||||
|
.map(|handle| handle.id.as_str())
|
||||||
|
.unwrap_or(target);
|
||||||
|
Ok(serde_json::json!({
|
||||||
|
"kind": "session_exists",
|
||||||
|
"session_id": resolved_id,
|
||||||
|
"requested": target,
|
||||||
|
"exists": exists,
|
||||||
|
"active": resolved_id == active_session_id,
|
||||||
|
"path": resolved
|
||||||
|
.as_ref()
|
||||||
|
.map(|handle| handle.path.display().to_string()),
|
||||||
|
"candidate_path": handle.path.display().to_string(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_resumed_session_command(
|
||||||
|
session_path: &Path,
|
||||||
|
session: &Session,
|
||||||
|
action: Option<&str>,
|
||||||
|
target: Option<&str>,
|
||||||
|
) -> Result<ResumeCommandOutcome, Box<dyn std::error::Error>> {
|
||||||
|
match action {
|
||||||
|
None | Some("list") => {
|
||||||
|
let sessions = list_managed_sessions().unwrap_or_default();
|
||||||
|
let session_ids: Vec<String> = sessions.iter().map(|s| s.id.clone()).collect();
|
||||||
|
let active_id = session.session_id.clone();
|
||||||
|
let text = render_session_list(&active_id).unwrap_or_else(|e| format!("error: {e}"));
|
||||||
|
Ok(ResumeCommandOutcome {
|
||||||
|
session: session.clone(),
|
||||||
|
message: Some(text),
|
||||||
|
json: Some(serde_json::json!({
|
||||||
|
"kind": "session_list",
|
||||||
|
"sessions": session_ids,
|
||||||
|
"session_details": session_details_json(&sessions),
|
||||||
|
"active": active_id,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Some("exists") => {
|
||||||
|
let Some(target) = target else {
|
||||||
|
return Err("/session exists requires a session id".into());
|
||||||
|
};
|
||||||
|
let value = session_exists_json(target, &session.session_id)?;
|
||||||
|
let exists = value
|
||||||
|
.get("exists")
|
||||||
|
.and_then(|v| v.as_bool())
|
||||||
|
.unwrap_or(false);
|
||||||
|
Ok(ResumeCommandOutcome {
|
||||||
|
session: session.clone(),
|
||||||
|
message: Some(format!(
|
||||||
|
"Session exists\n Session {}\n Exists {}",
|
||||||
|
target,
|
||||||
|
if exists { "yes" } else { "no" }
|
||||||
|
)),
|
||||||
|
json: Some(value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Some("delete") => {
|
||||||
|
let Some(target) = target else {
|
||||||
|
return Err("/session delete requires a session id".into());
|
||||||
|
};
|
||||||
|
Ok(ResumeCommandOutcome {
|
||||||
|
session: session.clone(),
|
||||||
|
message: Some(format!(
|
||||||
|
"delete: confirmation required; rerun with /session delete {target} --force"
|
||||||
|
)),
|
||||||
|
json: Some(serde_json::json!({
|
||||||
|
"kind": "error",
|
||||||
|
"error": "confirmation required",
|
||||||
|
"hint": format!("rerun with /session delete {target} --force"),
|
||||||
|
"session_id": target,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Some("delete-force") => {
|
||||||
|
let Some(target) = target else {
|
||||||
|
return Err("/session delete requires a session id".into());
|
||||||
|
};
|
||||||
|
let handle = resolve_session_reference(target)?;
|
||||||
|
if handle.id == session.session_id || handle.path == session_path {
|
||||||
|
return Err(format!(
|
||||||
|
"delete: refusing to delete the active session '{}'. Resume or switch to another session first.",
|
||||||
|
handle.id
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
delete_managed_session(&handle.path)?;
|
||||||
|
Ok(ResumeCommandOutcome {
|
||||||
|
session: session.clone(),
|
||||||
|
message: Some(format!(
|
||||||
|
"Session deleted\n Deleted session {}\n File {}",
|
||||||
|
handle.id,
|
||||||
|
handle.path.display(),
|
||||||
|
)),
|
||||||
|
json: Some(serde_json::json!({
|
||||||
|
"kind": "session_delete",
|
||||||
|
"deleted": true,
|
||||||
|
"session_id": handle.id,
|
||||||
|
"path": handle.path.display().to_string(),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Some("switch" | "fork") => Err("unsupported resumed slash command".into()),
|
||||||
|
Some(other) => Err(format!("unsupported resumed /session action: {other}").into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_session_list(active_session_id: &str) -> Result<String, Box<dyn std::error::Error>> {
|
fn render_session_list(active_session_id: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
let sessions = list_managed_sessions()?;
|
let sessions = list_managed_sessions()?;
|
||||||
let mut lines = vec![
|
let mut lines = vec![
|
||||||
@ -13565,6 +13698,68 @@ UU conflicted.rs",
|
|||||||
std::fs::remove_dir_all(workspace).expect("workspace should clean up");
|
std::fs::remove_dir_all(workspace).expect("workspace should clean up");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resumed_session_exists_and_delete_have_json_contracts() {
|
||||||
|
let _guard = cwd_guard();
|
||||||
|
let workspace = temp_workspace("resume-session-json-contracts");
|
||||||
|
std::fs::create_dir_all(&workspace).expect("workspace should create");
|
||||||
|
let previous = std::env::current_dir().expect("cwd");
|
||||||
|
std::env::set_current_dir(&workspace).expect("switch cwd");
|
||||||
|
|
||||||
|
let active = create_managed_session_handle("session-active").expect("active handle");
|
||||||
|
let active_session = Session::new()
|
||||||
|
.with_workspace_root(workspace.clone())
|
||||||
|
.with_persistence_path(active.path.clone());
|
||||||
|
active_session
|
||||||
|
.save_to_path(&active.path)
|
||||||
|
.expect("active session should save");
|
||||||
|
let saved = create_managed_session_handle("session-saved").expect("saved handle");
|
||||||
|
Session::new()
|
||||||
|
.with_workspace_root(workspace.clone())
|
||||||
|
.with_persistence_path(saved.path.clone())
|
||||||
|
.save_to_path(&saved.path)
|
||||||
|
.expect("saved session should save");
|
||||||
|
|
||||||
|
let exists_command = SlashCommand::parse("/session exists session-saved")
|
||||||
|
.expect("parse should succeed")
|
||||||
|
.expect("command should exist");
|
||||||
|
let exists = run_resume_command(&active.path, &active_session, &exists_command)
|
||||||
|
.expect("exists should run")
|
||||||
|
.json
|
||||||
|
.expect("exists should return json");
|
||||||
|
assert_eq!(exists["kind"], "session_exists");
|
||||||
|
assert_eq!(exists["session_id"], "session-saved");
|
||||||
|
assert_eq!(exists["exists"], true);
|
||||||
|
assert_eq!(exists["active"], false);
|
||||||
|
assert!(exists["path"].as_str().is_some());
|
||||||
|
|
||||||
|
let missing_command = SlashCommand::parse("/session exists missing-session")
|
||||||
|
.expect("parse should succeed")
|
||||||
|
.expect("command should exist");
|
||||||
|
let missing = run_resume_command(&active.path, &active_session, &missing_command)
|
||||||
|
.expect("missing exists should run")
|
||||||
|
.json
|
||||||
|
.expect("missing exists should return json");
|
||||||
|
assert_eq!(missing["kind"], "session_exists");
|
||||||
|
assert_eq!(missing["exists"], false);
|
||||||
|
assert_eq!(missing["session_id"], "missing-session");
|
||||||
|
assert!(missing["candidate_path"].as_str().is_some());
|
||||||
|
|
||||||
|
let delete_command = SlashCommand::parse("/session delete session-saved --force")
|
||||||
|
.expect("parse should succeed")
|
||||||
|
.expect("command should exist");
|
||||||
|
let deleted = run_resume_command(&active.path, &active_session, &delete_command)
|
||||||
|
.expect("delete should run")
|
||||||
|
.json
|
||||||
|
.expect("delete should return json");
|
||||||
|
assert_eq!(deleted["kind"], "session_delete");
|
||||||
|
assert_eq!(deleted["deleted"], true);
|
||||||
|
assert!(!saved.path.exists(), "saved session should be deleted");
|
||||||
|
|
||||||
|
std::env::set_current_dir(previous).expect("restore cwd");
|
||||||
|
std::fs::remove_dir_all(workspace).expect("workspace should clean up");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn latest_session_alias_resolves_most_recent_managed_session() {
|
fn latest_session_alias_resolves_most_recent_managed_session() {
|
||||||
let _guard = cwd_guard();
|
let _guard = cwd_guard();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user