From 6df3f7ae623ba2752e563ade68844be75043b9df Mon Sep 17 00:00:00 2001 From: luca-ctx <216224554+luca-ctx@users.noreply.github.com> Date: Wed, 1 Jul 2026 18:57:40 -0500 Subject: [PATCH] release: bump ctx to 0.15.0 --- Cargo.lock | 10 +- crates/ctx-cli/Cargo.toml | 2 +- crates/ctx-history-capture/Cargo.toml | 2 +- crates/ctx-history-capture/src/lib.rs | 351 ++++++++++-------- .../src/provider_sources.rs | 8 +- crates/ctx-history-core/Cargo.toml | 2 +- crates/ctx-history-search/Cargo.toml | 2 +- crates/ctx-history-store/Cargo.toml | 2 +- scripts/build-public-cli-artifact.sh | 10 +- 9 files changed, 214 insertions(+), 175 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 906814d5..eabab954 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,7 +256,7 @@ dependencies = [ [[package]] name = "ctx" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "assert_cmd", @@ -281,7 +281,7 @@ dependencies = [ [[package]] name = "ctx-history-capture" -version = "0.14.0" +version = "0.15.0" dependencies = [ "chrono", "ctx-history-core", @@ -296,7 +296,7 @@ dependencies = [ [[package]] name = "ctx-history-core" -version = "0.14.0" +version = "0.15.0" dependencies = [ "chrono", "directories", @@ -309,7 +309,7 @@ dependencies = [ [[package]] name = "ctx-history-search" -version = "0.14.0" +version = "0.15.0" dependencies = [ "chrono", "ctx-history-core", @@ -324,7 +324,7 @@ dependencies = [ [[package]] name = "ctx-history-store" -version = "0.14.0" +version = "0.15.0" dependencies = [ "chrono", "ctx-history-core", diff --git a/crates/ctx-cli/Cargo.toml b/crates/ctx-cli/Cargo.toml index 996d0ad6..2b7ed2b2 100644 --- a/crates/ctx-cli/Cargo.toml +++ b/crates/ctx-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ctx" -version = "0.14.0" +version = "0.15.0" description = "Local CLI for indexing and searching agent session history" edition.workspace = true autobins = false diff --git a/crates/ctx-history-capture/Cargo.toml b/crates/ctx-history-capture/Cargo.toml index 9c5967f2..35751e60 100644 --- a/crates/ctx-history-capture/Cargo.toml +++ b/crates/ctx-history-capture/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ctx-history-capture" -version = "0.14.0" +version = "0.15.0" description = "Internal provider import adapters for ctx local agent history" edition.workspace = true license.workspace = true diff --git a/crates/ctx-history-capture/src/lib.rs b/crates/ctx-history-capture/src/lib.rs index e656bfda..638f2688 100644 --- a/crates/ctx-history-capture/src/lib.rs +++ b/crates/ctx-history-capture/src/lib.rs @@ -5434,10 +5434,10 @@ fn hermes_decode_content(raw: Option<&str>) -> Value { Value::String(raw.to_owned()) } -fn native_event( +struct NativeEventDraft { provider: CaptureProvider, source_format: &'static str, - provider_session_id: &str, + provider_session_id: String, provider_event_index: u64, provider_event_hash: Option, cursor: String, @@ -5447,31 +5447,33 @@ fn native_event( text: String, body: Value, metadata: Value, -) -> ProviderEventEnvelope { - let (text, truncated) = provider_safe_preview(&text, PROVIDER_MAX_TEXT_CHARS); +} + +fn native_event(draft: NativeEventDraft) -> ProviderEventEnvelope { + let (text, truncated) = provider_safe_preview(&draft.text, PROVIDER_MAX_TEXT_CHARS); ProviderEventEnvelope { - provider_event_index, - provider_event_hash, - cursor: Some(cursor), - event_type, - role, - occurred_at, + provider_event_index: draft.provider_event_index, + provider_event_hash: draft.provider_event_hash, + cursor: Some(draft.cursor), + event_type: draft.event_type, + role: draft.role, + occurred_at: draft.occurred_at, fidelity: Fidelity::Imported, redaction_state: RedactionState::SafePreview, idempotency_key: Some(format!( "provider-event:{}:{}:{}", - provider.as_str(), - provider_session_id, - provider_event_index + draft.provider.as_str(), + draft.provider_session_id, + draft.provider_event_index )), artifacts: Vec::new(), payload: json!({ "text": text, "truncated": truncated, - "source_format": source_format, - "body": provider_capped_json(&body, PROVIDER_MAX_PREVIEW_CHARS), + "source_format": draft.source_format, + "body": provider_capped_json(&draft.body, PROVIDER_MAX_PREVIEW_CHARS), }), - metadata, + metadata: draft.metadata, } } @@ -5669,16 +5671,18 @@ fn normalize_openclaw_jsonl_file( result.captures.push(( line_number, openclaw_capture( - &provider_session_id, - agent_id.as_deref(), - started_at, - None, - cwd.clone(), - path, + OpenClawCaptureDraft { + provider_session_id: &provider_session_id, + agent_id: agent_id.as_deref(), + started_at, + ended_at: None, + cwd: cwd.clone(), + path, + indexes, + header_raw: header_raw.clone(), + event: None, + }, context, - indexes, - header_raw.clone(), - None, ), )); continue; @@ -5698,50 +5702,69 @@ fn normalize_openclaw_jsonl_file( result.captures.push(( line_number, openclaw_capture( - &provider_session_id, - agent_id.as_deref(), - started_at, - None, - cwd.clone(), - path, + OpenClawCaptureDraft { + provider_session_id: &provider_session_id, + agent_id: agent_id.as_deref(), + started_at, + ended_at: None, + cwd: cwd.clone(), + path, + indexes, + header_raw: header_raw.clone(), + event: None, + }, context, - indexes, - header_raw.clone(), - None, ), )); } result.captures.push(( line_number, openclaw_capture( - &provider_session_id, - agent_id.as_deref(), - started_at, - None, - cwd.clone(), - path, + OpenClawCaptureDraft { + provider_session_id: &provider_session_id, + agent_id: agent_id.as_deref(), + started_at, + ended_at: None, + cwd: cwd.clone(), + path, + indexes, + header_raw: header_raw.clone(), + event: Some(event), + }, context, - indexes, - header_raw.clone(), - Some(event), ), )); } Ok(result) } -fn openclaw_capture( - provider_session_id: &str, - agent_id: Option<&str>, +struct OpenClawCaptureDraft<'a> { + provider_session_id: &'a str, + agent_id: Option<&'a str>, started_at: DateTime, ended_at: Option>, cwd: Option, - path: &Path, - context: &ProviderAdapterContext, - indexes: &BTreeMap, + path: &'a Path, + indexes: &'a BTreeMap, header_raw: Value, event: Option, +} + +fn openclaw_capture( + draft: OpenClawCaptureDraft<'_>, + context: &ProviderAdapterContext, ) -> ProviderCaptureEnvelope { + let OpenClawCaptureDraft { + provider_session_id, + agent_id, + started_at, + ended_at, + cwd, + path, + indexes, + header_raw, + event, + } = draft; let local_id = provider_session_id .rsplit_once('/') .map(|(_, id)| id) @@ -5818,26 +5841,26 @@ fn openclaw_event( .or_else(|| message.get("output")) .and_then(provider_value_text) .unwrap_or_else(|| format!("OpenClaw {row_type}")); - native_event( - CaptureProvider::OpenClaw, - OPENCLAW_SOURCE_FORMAT, - provider_session_id, - event_index, - row.get("id").and_then(Value::as_str).map(str::to_owned), - format!("line:{line_number}"), + native_event(NativeEventDraft { + provider: CaptureProvider::OpenClaw, + source_format: OPENCLAW_SOURCE_FORMAT, + provider_session_id: provider_session_id.to_owned(), + provider_event_index: event_index, + provider_event_hash: row.get("id").and_then(Value::as_str).map(str::to_owned), + cursor: format!("line:{line_number}"), event_type, role, occurred_at, text, - row.clone(), - json!({ + body: row.clone(), + metadata: json!({ "source": "openclaw_jsonl", "source_format": OPENCLAW_SOURCE_FORMAT, "row_type": row_type, "message_id": row.get("id").and_then(Value::as_str), "parent_id": row.get("parentId").or_else(|| row.get("parent_id")).cloned(), }), - ) + }) } #[derive(Debug, Clone)] @@ -5934,18 +5957,18 @@ fn normalize_hermes_sqlite( }); let event_type = hermes_event_type(&row); let role = Some(provider_role(Some(&row.role))); - let event = native_event( - CaptureProvider::Hermes, - HERMES_SQLITE_SOURCE_FORMAT, - &provider_session_id, - row.id.max(0) as u64, - Some(format!("message:{}", row.id)), - format!("messages:id:{}", row.id), + let event = native_event(NativeEventDraft { + provider: CaptureProvider::Hermes, + source_format: HERMES_SQLITE_SOURCE_FORMAT, + provider_session_id: provider_session_id.clone(), + provider_event_index: row.id.max(0) as u64, + provider_event_hash: Some(format!("message:{}", row.id)), + cursor: format!("messages:id:{}", row.id), event_type, role, occurred_at, text, - json!({ + body: json!({ "message_id": row.id, "role": row.role, "content": content, @@ -5958,7 +5981,7 @@ fn normalize_hermes_sqlite( "codex_reasoning_items": row.codex_reasoning_items.as_deref().map(provider_json_text), "codex_message_items": row.codex_message_items.as_deref().map(provider_json_text), }), - json!({ + metadata: json!({ "source": "hermes_state_db", "source_format": HERMES_SQLITE_SOURCE_FORMAT, "message_id": row.id, @@ -5969,7 +5992,7 @@ fn normalize_hermes_sqlite( "active": row.active != 0, "compacted": row.compacted != 0, }), - ); + }); result.captures.push(( row.id.max(0) as usize, native_provider_capture( @@ -6291,23 +6314,23 @@ fn normalize_nanoclaw_project( } else { Some(EventRole::Assistant) }; - let event = native_event( - CaptureProvider::NanoClaw, - NANOCLAW_SOURCE_FORMAT, - &provider_session_id, - event_index, - Some(format!("{}:{}", message.source, message.id)), - format!( + let event = native_event(NativeEventDraft { + provider: CaptureProvider::NanoClaw, + source_format: NANOCLAW_SOURCE_FORMAT, + provider_session_id: provider_session_id.clone(), + provider_event_index: event_index, + provider_event_hash: Some(format!("{}:{}", message.source, message.id)), + cursor: format!( "{}:{}:{}", message.source, session.id, message.seq.unwrap_or_default() ), - EventType::Message, + event_type: EventType::Message, role, occurred_at, text, - json!({ + body: json!({ "message_id": message.id, "seq": message.seq, "kind": message.kind, @@ -6321,13 +6344,13 @@ fn normalize_nanoclaw_project( "source_session_id": message.source_session_id, "on_wake": message.on_wake, }), - json!({ + metadata: json!({ "source": format!("nanoclaw_{}", message.source), "source_format": NANOCLAW_SOURCE_FORMAT, "message_id": message.id, "seq": message.seq, }), - ); + }); result.captures.push(( event_index.min(usize::MAX as u64) as usize, native_provider_capture( @@ -6655,76 +6678,81 @@ fn normalize_astrbot_sqlite( let role = astrbot_role(item); let text = astrbot_item_text(item) .unwrap_or_else(|| "AstrBot conversation item".to_owned()); - let event = native_event( - CaptureProvider::AstrBot, - ASTRBOT_SQLITE_SOURCE_FORMAT, - &provider_session_id, - index as u64, - astrbot_item_id(item).map(|id| format!("conversation:{id}")), - format!("conversation:{}:item:{index}", conversation.conversation_id), - EventType::Message, + let event = native_event(NativeEventDraft { + provider: CaptureProvider::AstrBot, + source_format: ASTRBOT_SQLITE_SOURCE_FORMAT, + provider_session_id: provider_session_id.clone(), + provider_event_index: index as u64, + provider_event_hash: astrbot_item_id(item) + .map(|id| format!("conversation:{id}")), + cursor: format!("conversation:{}:item:{index}", conversation.conversation_id), + event_type: EventType::Message, role, - started_at, + occurred_at: started_at, text, - item.clone(), - json!({ + body: item.clone(), + metadata: json!({ "source": "astrbot_conversations", "source_format": ASTRBOT_SQLITE_SOURCE_FORMAT, "conversation_id": conversation.conversation_id, "inner_conversation_id": conversation.inner_conversation_id, "item_index": index, }), - ); + }); result.captures.push(( index + 1, astrbot_capture( - conversation, - &provider_session_id, - started_at, - ended_at, - path, + AstrBotCaptureDraft { + conversation, + provider_session_id: &provider_session_id, + started_at, + ended_at, + path, + user_version, + schema_fingerprint: &schema_fingerprint, + selected_conversation: selected_conversation.as_deref(), + event: Some(event), + }, context, - user_version, - &schema_fingerprint, - selected_conversation.as_deref(), - Some(event), ), )); } } else { let text = provider_value_text(&content).unwrap_or_else(|| "AstrBot conversation".to_owned()); - let event = native_event( - CaptureProvider::AstrBot, - ASTRBOT_SQLITE_SOURCE_FORMAT, - &provider_session_id, - 0, - Some(format!("conversation-row:{}", conversation.row_id)), - format!("conversation:{}:content", conversation.conversation_id), - EventType::Message, - None, - started_at, + let event = native_event(NativeEventDraft { + provider: CaptureProvider::AstrBot, + source_format: ASTRBOT_SQLITE_SOURCE_FORMAT, + provider_session_id: provider_session_id.clone(), + provider_event_index: 0, + provider_event_hash: Some(format!("conversation-row:{}", conversation.row_id)), + cursor: format!("conversation:{}:content", conversation.conversation_id), + event_type: EventType::Message, + role: None, + occurred_at: started_at, text, - content.clone(), - json!({ + body: content.clone(), + metadata: json!({ "source": "astrbot_conversations", "source_format": ASTRBOT_SQLITE_SOURCE_FORMAT, "conversation_id": conversation.conversation_id, }), - ); + }); result.captures.push(( conversation.row_id.max(0) as usize, astrbot_capture( - conversation, - &provider_session_id, - started_at, - ended_at, - path, + AstrBotCaptureDraft { + conversation, + provider_session_id: &provider_session_id, + started_at, + ended_at, + path, + user_version, + schema_fingerprint: &schema_fingerprint, + selected_conversation: selected_conversation.as_deref(), + event: Some(event), + }, context, - user_version, - &schema_fingerprint, - selected_conversation.as_deref(), - Some(event), ), )); } @@ -6765,18 +6793,18 @@ fn normalize_astrbot_sqlite( Some(EventRole::Assistant) }; let event_index = 1_000_000u64.saturating_add(message.id.max(0) as u64); - let event = native_event( - CaptureProvider::AstrBot, - ASTRBOT_SQLITE_SOURCE_FORMAT, - &provider_session_id, - event_index, - Some(format!("platform-message:{}", message.id)), - format!("platform_message_history:id:{}", message.id), - EventType::Message, + let event = native_event(NativeEventDraft { + provider: CaptureProvider::AstrBot, + source_format: ASTRBOT_SQLITE_SOURCE_FORMAT, + provider_session_id: provider_session_id.clone(), + provider_event_index: event_index, + provider_event_hash: Some(format!("platform-message:{}", message.id)), + cursor: format!("platform_message_history:id:{}", message.id), + event_type: EventType::Message, role, - provider_timestamp_millis(message.created_at, started_at), + occurred_at: provider_timestamp_millis(message.created_at, started_at), text, - json!({ + body: json!({ "message_id": message.id, "platform_id": message.platform_id, "user_id": message.user_id, @@ -6785,28 +6813,30 @@ fn normalize_astrbot_sqlite( "content": content, "llm_checkpoint_id": message.llm_checkpoint_id, }), - json!({ + metadata: json!({ "source": "astrbot_platform_message_history", "source_format": ASTRBOT_SQLITE_SOURCE_FORMAT, "message_id": message.id, }), - ); + }); if let Some(conversation) = conversation { result.captures.push(( event_index.min(usize::MAX as u64) as usize, astrbot_capture( - conversation, - &provider_session_id, - started_at, - conversation.updated_at.map(|timestamp| { - provider_timestamp_millis(Some(timestamp), context.imported_at) - }), - path, + AstrBotCaptureDraft { + conversation, + provider_session_id: &provider_session_id, + started_at, + ended_at: conversation.updated_at.map(|timestamp| { + provider_timestamp_millis(Some(timestamp), context.imported_at) + }), + path, + user_version, + schema_fingerprint: &schema_fingerprint, + selected_conversation: selected_conversation.as_deref(), + event: Some(event), + }, context, - user_version, - &schema_fingerprint, - selected_conversation.as_deref(), - Some(event), ), )); } else { @@ -6861,18 +6891,33 @@ fn astrbot_provider_session_id(conversation: &AstrBotConversationRow) -> String .unwrap_or_else(|| format!("conversation-row-{}", conversation.row_id)) } -fn astrbot_capture( - conversation: &AstrBotConversationRow, - provider_session_id: &str, +struct AstrBotCaptureDraft<'a> { + conversation: &'a AstrBotConversationRow, + provider_session_id: &'a str, started_at: DateTime, ended_at: Option>, - path: &Path, - context: &ProviderAdapterContext, + path: &'a Path, user_version: i64, - schema_fingerprint: &str, - selected_conversation: Option<&str>, + schema_fingerprint: &'a str, + selected_conversation: Option<&'a str>, event: Option, +} + +fn astrbot_capture( + draft: AstrBotCaptureDraft<'_>, + context: &ProviderAdapterContext, ) -> ProviderCaptureEnvelope { + let AstrBotCaptureDraft { + conversation, + provider_session_id, + started_at, + ended_at, + path, + user_version, + schema_fingerprint, + selected_conversation, + event, + } = draft; native_provider_capture( NativeSessionDraft { provider: CaptureProvider::AstrBot, diff --git a/crates/ctx-history-capture/src/provider_sources.rs b/crates/ctx-history-capture/src/provider_sources.rs index 2c3ca174..6834e29b 100644 --- a/crates/ctx-history-capture/src/provider_sources.rs +++ b/crates/ctx-history-capture/src/provider_sources.rs @@ -499,13 +499,7 @@ pub fn provider_source_for_path(provider: CaptureProvider, path: PathBuf) -> Pro CaptureProvider::FactoryAiDroid => "factory_ai_droid_sessions_jsonl", CaptureProvider::OpenClaw => "openclaw_session_jsonl_tree", CaptureProvider::Hermes => "hermes_state_sqlite", - CaptureProvider::NanoClaw => { - if path.file_name().and_then(|name| name.to_str()) == Some("v2.db") { - "nanoclaw_project" - } else { - "nanoclaw_project" - } - } + CaptureProvider::NanoClaw => "nanoclaw_project", CaptureProvider::AstrBot => "astrbot_data_v4_sqlite", _ => "unsupported", }; diff --git a/crates/ctx-history-core/Cargo.toml b/crates/ctx-history-core/Cargo.toml index aeea64c2..e6ffa89f 100644 --- a/crates/ctx-history-core/Cargo.toml +++ b/crates/ctx-history-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ctx-history-core" -version = "0.14.0" +version = "0.15.0" description = "Internal core types for ctx local agent history indexing" edition.workspace = true license.workspace = true diff --git a/crates/ctx-history-search/Cargo.toml b/crates/ctx-history-search/Cargo.toml index 7464db47..0e4d93b8 100644 --- a/crates/ctx-history-search/Cargo.toml +++ b/crates/ctx-history-search/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ctx-history-search" -version = "0.14.0" +version = "0.15.0" description = "Internal search projection and ranking helpers for ctx" edition.workspace = true license.workspace = true diff --git a/crates/ctx-history-store/Cargo.toml b/crates/ctx-history-store/Cargo.toml index cbf8f966..02d1155a 100644 --- a/crates/ctx-history-store/Cargo.toml +++ b/crates/ctx-history-store/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ctx-history-store" -version = "0.14.0" +version = "0.15.0" description = "Internal SQLite storage layer for ctx local agent history" edition.workspace = true license.workspace = true diff --git a/scripts/build-public-cli-artifact.sh b/scripts/build-public-cli-artifact.sh index 16b66e52..0e7b6cb9 100755 --- a/scripts/build-public-cli-artifact.sh +++ b/scripts/build-public-cli-artifact.sh @@ -113,8 +113,8 @@ ensure_darwin_cross_tools() { } version="$(cargo metadata --no-deps --format-version 1 | python3 -c 'import json,sys; data=json.load(sys.stdin); print(next(pkg["version"] for pkg in data["packages"] if pkg["name"] == "ctx"))')" -if [[ "${version}" != "0.14.0" ]]; then - echo "error: ctx package version must be 0.14.0 for this release, got ${version}" >&2 +if [[ "${version}" != "0.15.0" ]]; then + echo "error: ctx package version must be 0.15.0 for this release, got ${version}" >&2 exit 1 fi @@ -156,12 +156,12 @@ fi case "${platform}" in linux-x64) "${staged}" --version | tee "${staged}.version" - grep -Fx "ctx 0.14.0" "${staged}.version" >/dev/null + grep -Fx "ctx 0.15.0" "${staged}.version" >/dev/null ;; macos-arm64) if [[ "$(uname -s)" == "Darwin" && "$(uname -m)" == "arm64" ]]; then "${staged}" --version | tee "${staged}.version" - grep -Fx "ctx 0.14.0" "${staged}.version" >/dev/null + grep -Fx "ctx 0.15.0" "${staged}.version" >/dev/null else printf 'not run on this host: %s\n' "${platform}" > "${staged}.version" fi @@ -169,7 +169,7 @@ case "${platform}" in macos-x64) if [[ "$(uname -s)" == "Darwin" ]] && /usr/bin/arch -x86_64 /usr/bin/true >/dev/null 2>&1; then /usr/bin/arch -x86_64 "${staged}" --version | tee "${staged}.version" - grep -Fx "ctx 0.14.0" "${staged}.version" >/dev/null + grep -Fx "ctx 0.15.0" "${staged}.version" >/dev/null else printf 'not run on this host: %s\n' "${platform}" > "${staged}.version" fi