Skip to main content

supercode_interchange/session/
openclaw.rs

1//! OpenClaw session codec: loader, key parsing and header nouns.
2
3use super::*;
4
5pub use crate::ontology::parse_openclaw_session_key;
6use crate::ontology::Binding;
7
8/// OpenClaw sessions live under `agents/<agentId>/…`; the agent id is the profile.
9pub fn openclaw_agent_id_from_path(path: &std::path::Path) -> Option<String> {
10    let comps: Vec<String> = path
11        .components()
12        .map(|c| c.as_os_str().to_string_lossy().into_owned())
13        .collect();
14    comps
15        .windows(2)
16        .find(|w| w[0] == "agents")
17        .map(|w| w[1].clone())
18}
19
20impl Session {
21    /// Load an OpenClaw agent session (`~/.openclaw/agents/<id>/sessions/
22    /// <uuid>.jsonl`, openclaw >= 2026.7): pi session-format v3 with the
23    /// openclaw dialect divergences (READ-ONLY tier, UNI-16):
24    ///
25    /// - `type:"leaf"` navigation-control entries REDIRECT the active leaf to
26    ///   their `targetId` — pi's own "last entry in file order" anchor rule is
27    ///   wrong for them, so the LAST leaf entry wins (a dangling or null
28    ///   `targetId` fails safe to the default rule).
29    /// - `appendMode:"side"` entries never become the anchor.
30    /// - Vendor-namespaced `__openclaw` message metadata is stamped onto the
31    ///   loaded message as `openclaw_<key>` metadata — provenance preserved,
32    ///   never interpreted.
33    ///
34    /// A session file that references OTHER sessions (delegate/session-key
35    /// markers) decodes STANDALONE: references survive as metadata/raw only
36    /// and are never dereferenced — a nested-runtime transcript is a mirror,
37    /// never a recursive load.
38    pub fn from_openclaw_str(jsonl: &str) -> Result<Session> {
39        Self::from_pi_v3_dialect(jsonl, SessionSource::OpenClaw, true)
40    }
41}
42
43/// ORCH-3: an OpenClaw transcript header may carry the gateway session key
44/// (`sessionKey` at the top level or under `__openclaw`); derive the nouns
45/// from it. Absent key → nothing is claimed.
46pub(crate) fn openclaw_capture_header_nouns(header: &Value, meta: &mut SessionMeta) {
47    let key = header
48        .get("sessionKey")
49        .or_else(|| header.get("__openclaw").and_then(|o| o.get("sessionKey")))
50        .and_then(Value::as_str);
51    let Some(key) = key else { return };
52    if let Some(binding) = Binding::from_openclaw_key(key, None, None, None) {
53        if meta.profile.is_none() {
54            meta.profile = binding.profile.clone();
55        }
56        meta.surface = Some(binding.key.clone());
57        meta.trigger = Some(binding.trigger);
58        meta.recurrence = binding.recurrence;
59    }
60}