chore: import upstream snapshot with attribution
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:10:34 +08:00
commit a789495a98
1551 changed files with 718128 additions and 0 deletions
+289
View File
@@ -0,0 +1,289 @@
use chrono::{DateTime, Utc};
use jcode_message_types::ToolCall;
use jcode_session_types::SessionStatus;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SessionSource {
Jcode,
ClaudeCode,
Codex,
Pi,
OpenCode,
Cursor,
}
impl SessionSource {
pub fn badge(self) -> Option<&'static str> {
match self {
Self::Jcode => None,
Self::ClaudeCode => Some("🧵 Claude Code"),
Self::Codex => Some("🧠 Codex"),
Self::Pi => Some("π Pi"),
Self::OpenCode => Some("◌ OpenCode"),
Self::Cursor => Some("▮ Cursor"),
}
}
}
// `ResumeTarget` is pure data and now lives in `jcode-session-types` so the
// foundation/import layer can use it without depending on this UI crate. It is
// re-exported here so existing `jcode_tui_session_picker::ResumeTarget` paths
// keep working.
pub use jcode_session_types::ResumeTarget;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SessionFilterMode {
All,
CatchUp,
Saved,
/// Sessions with a live process right now (from the active-pid registry),
/// annotated with whether each is still streaming a response or is ready
/// for input. Backs the opt-in "active sessions manager" view.
Active,
ClaudeCode,
Codex,
Pi,
OpenCode,
Cursor,
/// External CLI transcripts (Codex and/or Claude Code) shown together.
/// Used by the first-run onboarding "continue where you left off" picker so
/// it surfaces every external CLI the user is logged into, not just one.
ExternalClis,
}
impl SessionFilterMode {
pub fn next(self) -> Self {
match self {
Self::All => Self::CatchUp,
Self::CatchUp => Self::Saved,
Self::Saved => Self::Active,
Self::Active => Self::ClaudeCode,
Self::ClaudeCode => Self::Codex,
Self::Codex => Self::Pi,
Self::Pi => Self::OpenCode,
Self::OpenCode => Self::Cursor,
Self::Cursor => Self::All,
// ExternalClis is an onboarding-only composite filter, not part of
// the user-facing cycle; treat it as a no-op anchor.
Self::ExternalClis => Self::All,
}
}
pub fn previous(self) -> Self {
match self {
Self::All => Self::Cursor,
Self::CatchUp => Self::All,
Self::Saved => Self::CatchUp,
Self::Active => Self::Saved,
Self::ClaudeCode => Self::Active,
Self::Codex => Self::ClaudeCode,
Self::Pi => Self::Codex,
Self::OpenCode => Self::Pi,
Self::Cursor => Self::OpenCode,
Self::ExternalClis => Self::All,
}
}
pub fn label(self) -> Option<&'static str> {
match self {
Self::All => None,
Self::CatchUp => Some("⏭ catch up"),
Self::Saved => Some("📌 saved"),
Self::Active => Some("⚡ active"),
Self::ClaudeCode => Some("🧵 Claude Code"),
Self::Codex => Some("🧠 Codex"),
Self::Pi => Some("π Pi"),
Self::OpenCode => Some("◌ OpenCode"),
Self::Cursor => Some("▮ Cursor"),
Self::ExternalClis => Some("🧠 Codex + 🧵 Claude Code + π Pi + ◌ OpenCode + ▮ Cursor"),
}
}
}
/// Session info for display in the interactive session picker.
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SessionInfo {
pub id: String,
pub parent_id: Option<String>,
pub short_name: String,
pub icon: String,
pub title: String,
pub message_count: usize,
pub user_message_count: usize,
pub assistant_message_count: usize,
pub created_at: DateTime<Utc>,
pub last_message_time: DateTime<Utc>,
pub last_active_at: Option<DateTime<Utc>>,
pub working_dir: Option<String>,
pub model: Option<String>,
pub provider_key: Option<String>,
pub is_canary: bool,
pub is_debug: bool,
pub saved: bool,
pub save_label: Option<String>,
pub status: SessionStatus,
pub needs_catchup: bool,
pub estimated_tokens: usize,
/// First visible user prompt in the session, shown in compact list rows.
pub first_user_prompt: Option<String>,
pub messages_preview: Vec<PreviewMessage>,
/// Lowercased searchable text used by picker filtering.
pub search_index: String,
/// Server name this session belongs to (if running).
pub server_name: Option<String>,
/// Server icon.
pub server_icon: Option<String>,
/// Human/session source classification shown in the UI.
pub source: SessionSource,
/// How this entry should be resumed when selected.
pub resume_target: ResumeTarget,
/// Backing external transcript/storage path when available.
pub external_path: Option<String>,
}
/// A group of sessions under a server.
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ServerGroup {
pub name: String,
pub icon: String,
pub version: String,
pub git_hash: String,
pub is_running: bool,
pub sessions: Vec<SessionInfo>,
}
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PreviewMessage {
pub role: String,
pub content: String,
pub tool_calls: Vec<String>,
pub tool_data: Option<ToolCall>,
pub timestamp: Option<DateTime<Utc>>,
}
/// An item in the picker list, either a server/header row or a session row.
#[derive(Clone)]
pub enum PickerItem {
ServerHeader {
name: String,
icon: String,
version: String,
session_count: usize,
},
Session,
OrphanHeader {
session_count: usize,
},
SavedHeader {
session_count: usize,
},
}
pub fn session_is_claude_code(source: SessionSource, id: &str) -> bool {
source == SessionSource::ClaudeCode || id.starts_with("imported_cc_")
}
pub fn session_is_codex(source: SessionSource, model: Option<&str>) -> bool {
if source == SessionSource::Codex {
return true;
}
model
.map(|model| model.to_ascii_lowercase().contains("codex"))
.unwrap_or(false)
}
pub fn session_is_pi(
source: SessionSource,
provider_key: Option<&str>,
model: Option<&str>,
) -> bool {
if source == SessionSource::Pi {
return true;
}
let provider_matches = provider_key
.map(|key| {
let key = key.to_ascii_lowercase();
key == "pi" || key.starts_with("pi-")
})
.unwrap_or(false);
let model_matches = model
.map(|model| {
let model = model.to_ascii_lowercase();
model == "pi"
|| model.starts_with("pi-")
|| model.starts_with("pi/")
|| model.contains("/pi-")
})
.unwrap_or(false);
provider_matches || model_matches
}
pub fn session_is_open_code(source: SessionSource, provider_key: Option<&str>) -> bool {
if source == SessionSource::OpenCode {
return true;
}
provider_key
.map(|key| {
let key = key.to_ascii_lowercase();
key == "opencode" || key == "opencode-go" || key.contains("opencode")
})
.unwrap_or(false)
}
pub fn session_is_cursor(source: SessionSource, provider_key: Option<&str>) -> bool {
if source == SessionSource::Cursor {
return true;
}
provider_key
.map(|key| {
let key = key.to_ascii_lowercase();
key == "cursor" || key == "cursor-agent"
})
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resume_target_stable_id_uses_durable_identifier() {
let target = ResumeTarget::CodexSession {
session_id: "abc".into(),
session_path: "/tmp/session.json".into(),
};
assert_eq!(target.stable_id(), "abc");
let target = ResumeTarget::PiSession {
session_path: "/tmp/pi.jsonl".into(),
};
assert_eq!(target.stable_id(), "/tmp/pi.jsonl");
}
#[test]
fn source_predicates_cover_provider_and_model_fallbacks() {
assert!(session_is_claude_code(
SessionSource::Jcode,
"imported_cc_123"
));
assert!(session_is_codex(
SessionSource::Jcode,
Some("openai/codex-mini")
));
assert!(session_is_pi(SessionSource::Jcode, Some("pi-main"), None));
assert!(session_is_pi(
SessionSource::Jcode,
None,
Some("vendor/pi-fast")
));
assert!(session_is_open_code(
SessionSource::Jcode,
Some("opencode-go")
));
}
}