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}