diff --git a/CHANGELOG.md b/CHANGELOG.md index 75f31478..debdf711 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,37 @@ All notable changes to the Toolpath workspace are documented here. +## Harness-generated turns take the harness actor — 2026-08-06 + +A harness sometimes emits an assistant message itself — an API error, a +rate-limit notice, a timeout — with no model call behind it. Claude Code +marks these with a placeholder in `message.model`, and `derive_path` +took that placeholder at face value: the step landed on an `agent:` +actor naming a string that is not a model, and `meta.actors` described +the harness as if it were one. The actor convention never meant that — +`agent:` is "a model reply, named by the recorded model", and a +placeholder is neither a model nor a name for one. + +- **`toolpath-convo`** (0.12.0): `Turn` gains a provider-agnostic + `harness_generated` flag (serde-optional, omitted when false, so the + wire format stays byte-compatible with producers that predate it). + `derive_path` attributes such turns to the harness actor + `tool:` — the same actor system and other roles already + take — instead of to an `agent:` actor. The turn's `role` stays + `assistant`, so the message keeps its place in the transcript; only + the attribution changes. `agent:unknown` is unaffected: "no model + recorded" and "no model involved" stay distinguishable. Minor bump — + a new public field on `Turn` breaks struct-literal construction. +- **`toolpath-claude`** (0.12.3): sets the flag from Claude Code's + placeholder model and leaves `Turn.model` empty, so the placeholder + string no longer reaches the IR. The placeholder is one harness's + format detail and stays owned by the crate that reads that format; + any provider can set the flag from whatever signal its own format + gives it, without touching shared code. + +No schema change: this is a deriver fix, valid under the existing +`agent-coding-session` kind. + ## Projected Claude sessions are resumable again — 2026-07-30 Two fixes found by live-resuming a projected session against the real diff --git a/Cargo.lock b/Cargo.lock index d9b355b7..80c224b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4073,7 +4073,7 @@ dependencies = [ [[package]] name = "toolpath-claude" -version = "0.12.2" +version = "0.12.3" dependencies = [ "anyhow", "chrono", @@ -4104,7 +4104,7 @@ dependencies = [ [[package]] name = "toolpath-convo" -version = "0.11.1" +version = "0.12.0" dependencies = [ "chrono", "jsonschema", diff --git a/Cargo.toml b/Cargo.toml index ec3e2606..8b6023db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,9 +25,9 @@ license = "Apache-2.0" [workspace.dependencies] toolpath = { version = "0.7.0", path = "crates/toolpath" } -toolpath-convo = { version = "0.11.1", path = "crates/toolpath-convo" } +toolpath-convo = { version = "0.12.0", path = "crates/toolpath-convo" } toolpath-git = { version = "0.6.0", path = "crates/toolpath-git" } -toolpath-claude = { version = "0.12.2", path = "crates/toolpath-claude", default-features = false } +toolpath-claude = { version = "0.12.3", path = "crates/toolpath-claude", default-features = false } toolpath-gemini = { version = "0.6.1", path = "crates/toolpath-gemini", default-features = false } toolpath-codex = { version = "0.6.1", path = "crates/toolpath-codex" } toolpath-copilot = { version = "0.1.0", path = "crates/toolpath-copilot" } diff --git a/crates/toolpath-claude/Cargo.toml b/crates/toolpath-claude/Cargo.toml index acb66c2f..a5c1caf3 100644 --- a/crates/toolpath-claude/Cargo.toml +++ b/crates/toolpath-claude/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "toolpath-claude" -version = "0.12.2" +version = "0.12.3" edition.workspace = true license.workspace = true repository = "https://github.com/empathic/toolpath" diff --git a/crates/toolpath-claude/src/project.rs b/crates/toolpath-claude/src/project.rs index 5435649a..31f8bf5a 100644 --- a/crates/toolpath-claude/src/project.rs +++ b/crates/toolpath-claude/src/project.rs @@ -1042,6 +1042,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -1062,6 +1063,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, diff --git a/crates/toolpath-claude/src/provider.rs b/crates/toolpath-claude/src/provider.rs index 0e544f96..b6ab4438 100644 --- a/crates/toolpath-claude/src/provider.rs +++ b/crates/toolpath-claude/src/provider.rs @@ -18,6 +18,12 @@ use toolpath_convo::{ // ── Conversion helpers ─────────────────────────────────────────────── +/// Claude Code's placeholder in `message.model` on assistant messages the +/// harness generated itself — API errors, rate-limit notices, timeouts — +/// where no model ran. It is a marker, not a model identifier, so it maps +/// to [`Turn::harness_generated`] and never into `Turn::model`. +const SYNTHETIC_MODEL: &str = ""; + fn claude_role_to_role(role: &MessageRole) -> Role { match role { MessageRole::User => Role::User, @@ -120,6 +126,8 @@ fn message_to_turn(entry: &ConversationEntry, msg: &Message) -> Turn { let delegations = extract_delegations(&tool_uses); + let harness_generated = msg.model.as_deref() == Some(SYNTHETIC_MODEL); + Turn { id: entry.uuid.clone(), parent_id: entry.parent_uuid.clone(), @@ -133,7 +141,12 @@ fn message_to_turn(entry: &ConversationEntry, msg: &Message) -> Turn { text, thinking, tool_uses, - model: msg.model.clone(), + model: if harness_generated { + None + } else { + msg.model.clone() + }, + harness_generated, stop_reason: msg.stop_reason.clone(), token_usage, attributed_token_usage: None, @@ -861,6 +874,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -1253,6 +1267,32 @@ mod tests { assert_eq!(turn.role, Role::User); } + #[test] + fn test_to_turn_maps_placeholder_model_to_harness_generated() { + // Claude Code's placeholder marks a message the harness produced + // itself; it is not a model identifier, so it becomes the flag and + // leaves `model` empty. + let entry: ConversationEntry = serde_json::from_str(&format!( + r#"{{"uuid":"u1","type":"assistant","timestamp":"2024-01-01T00:00:00Z","message":{{"role":"assistant","model":"{SYNTHETIC_MODEL}","content":[{{"type":"text","text":"API Error: Connection reset"}}]}}}}"# + )) + .unwrap(); + let turn = to_turn(&entry).unwrap(); + assert!(turn.harness_generated); + assert_eq!(turn.model, None); + assert_eq!(turn.role, Role::Assistant); + } + + #[test] + fn test_to_turn_keeps_a_real_model_and_leaves_the_flag_unset() { + let entry: ConversationEntry = serde_json::from_str( + r#"{"uuid":"u1","type":"assistant","timestamp":"2024-01-01T00:00:00Z","message":{"role":"assistant","model":"claude-opus-4-8","content":[{"type":"text","text":"on it"}]}}"#, + ) + .unwrap(); + let turn = to_turn(&entry).unwrap(); + assert!(!turn.harness_generated); + assert_eq!(turn.model.as_deref(), Some("claude-opus-4-8")); + } + #[test] fn test_to_turn_without_message() { let entry: ConversationEntry = serde_json::from_str( @@ -1455,6 +1495,7 @@ mod tests { }, ], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, diff --git a/crates/toolpath-codex/src/project.rs b/crates/toolpath-codex/src/project.rs index c2c26b82..eda3c914 100644 --- a/crates/toolpath-codex/src/project.rs +++ b/crates/toolpath-codex/src/project.rs @@ -720,6 +720,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -740,6 +741,7 @@ mod tests { thinking: None, tool_uses: vec![], model: Some("gpt-5.4".into()), + harness_generated: false, stop_reason: Some("stop".into()), token_usage: Some(TokenUsage { input_tokens: Some(100), diff --git a/crates/toolpath-codex/src/provider.rs b/crates/toolpath-codex/src/provider.rs index 5b915ce2..82086e89 100644 --- a/crates/toolpath-codex/src/provider.rs +++ b/crates/toolpath-codex/src/provider.rs @@ -827,6 +827,7 @@ fn message_to_turn( } else { None }, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -851,6 +852,7 @@ fn synthetic_assistant_turn( thinking: None, tool_uses: Vec::new(), model: model.map(str::to_string), + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, diff --git a/crates/toolpath-convo/Cargo.toml b/crates/toolpath-convo/Cargo.toml index 43652540..6ec4f1d0 100644 --- a/crates/toolpath-convo/Cargo.toml +++ b/crates/toolpath-convo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "toolpath-convo" -version = "0.11.1" +version = "0.12.0" edition.workspace = true license.workspace = true repository = "https://github.com/empathic/toolpath" diff --git a/crates/toolpath-convo/src/derive.rs b/crates/toolpath-convo/src/derive.rs index b0d13266..96003f21 100644 --- a/crates/toolpath-convo/src/derive.rs +++ b/crates/toolpath-convo/src/derive.rs @@ -519,6 +519,11 @@ fn serde_value_eq(a: &Step, b: &Step) -> bool { fn actor_for_turn(turn: &Turn, provider: &str) -> String { match &turn.role { Role::User => "human:user".to_string(), + // A harness-generated assistant message is the tool speaking, not a + // model, so it takes the same `tool:{provider}` actor as the System + // and Other roles. `agent:unknown` stays reserved for "no model + // recorded", which is a different state from "no model involved". + Role::Assistant if turn.harness_generated => format!("tool:{}", provider), Role::Assistant => { let model = turn.model.as_deref().unwrap_or("unknown"); format!("agent:{}", model) @@ -728,6 +733,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -949,11 +955,76 @@ mod tests { #[test] fn test_assistant_without_model() { let turn = base_turn("t1", Role::Assistant); + assert!(!turn.harness_generated); + assert_eq!(turn.model, None); let view = view_with(vec![turn]); let path = derive_path(&view, &DeriveConfig::default()); + // "No model recorded" is its own outcome, distinct from a + // harness-generated turn. assert_eq!(path.steps[0].step.actor, "agent:unknown"); } + #[test] + fn test_harness_generated_assistant_is_attributed_to_the_harness() { + let mut turn = base_turn("t1", Role::Assistant); + turn.harness_generated = true; + let view = view_with(vec![turn]); + let path = derive_path(&view, &DeriveConfig::default()); + + // The harness generated the message; no model produced it. + assert_eq!(path.steps[0].step.actor, "tool:pi"); + // Only attribution changes — the message keeps the assistant slot. + assert_eq!( + conv_change(&path.steps[0]).extra["role"], + serde_json::json!("assistant") + ); + + let actors = path.meta.as_ref().unwrap().actors.as_ref().unwrap(); + let actor = &actors["tool:pi"]; + assert_eq!(actor.provider.as_deref(), Some("pi")); + assert_eq!(actor.model, None); + } + + #[test] + fn test_harness_generated_turn_ignores_a_stray_model() { + // The flag is the authority: a harness-generated turn takes the + // harness actor even if the source recorded a model anyway. + let mut turn = base_turn("t1", Role::Assistant); + turn.harness_generated = true; + turn.model = Some("claude-opus-4-8".into()); + let view = view_with(vec![turn]); + let path = derive_path(&view, &DeriveConfig::default()); + + assert_eq!(path.steps[0].step.actor, "tool:pi"); + } + + #[test] + fn test_harness_generated_flag_is_omitted_when_false() { + // skip_serializing_if keeps the Turn wire format byte-compatible + // with producers that predate the flag. + let turn = base_turn("t1", Role::Assistant); + let json = serde_json::to_string(&turn).unwrap(); + assert!( + !json.contains("harness_generated"), + "false must be omitted, got: {json}" + ); + let back: Turn = serde_json::from_str(&json).unwrap(); + assert!(!back.harness_generated); + } + + #[test] + fn test_harness_generated_flag_round_trips_when_true() { + let mut turn = base_turn("t1", Role::Assistant); + turn.harness_generated = true; + let json = serde_json::to_string(&turn).unwrap(); + assert!( + json.contains("\"harness_generated\":true"), + "true must be written, got: {json}" + ); + let back: Turn = serde_json::from_str(&json).unwrap(); + assert!(back.harness_generated); + } + #[test] fn test_system_role() { let turn = base_turn("t1", Role::System); diff --git a/crates/toolpath-convo/src/extract.rs b/crates/toolpath-convo/src/extract.rs index c1d83800..78bacf2c 100644 --- a/crates/toolpath-convo/src/extract.rs +++ b/crates/toolpath-convo/src/extract.rs @@ -325,6 +325,7 @@ fn build_turn(step: &Step, extra: &HashMap) -> Turn { thinking, tool_uses, model, + harness_generated: false, stop_reason, token_usage, attributed_token_usage, diff --git a/crates/toolpath-convo/src/lib.rs b/crates/toolpath-convo/src/lib.rs index dcf3c3e2..14eec416 100644 --- a/crates/toolpath-convo/src/lib.rs +++ b/crates/toolpath-convo/src/lib.rs @@ -281,6 +281,14 @@ pub struct Turn { /// Model identifier (e.g. "claude-opus-4-6", "gpt-4o"). pub model: Option, + /// The harness produced this turn itself, with no model call — + /// injected errors, limit notices, timeouts. Such turns are + /// attributed to the harness actor rather than to a model, and their + /// [`Turn::model`] is ignored. Distinct from a turn whose source simply + /// records no model: "no model involved" is not "no model recorded". + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub harness_generated: bool, + /// Why the turn ended (e.g. "end_turn", "tool_use", "max_tokens"). pub stop_reason: Option, @@ -584,6 +592,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -610,6 +619,7 @@ mod tests { category: Some(ToolCategory::FileRead), }], model: Some("claude-opus-4-6".into()), + harness_generated: false, stop_reason: Some("end_turn".into()), token_usage: Some(TokenUsage { input_tokens: Some(100), @@ -633,6 +643,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -973,6 +984,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, diff --git a/crates/toolpath-convo/src/project.rs b/crates/toolpath-convo/src/project.rs index 3a0a0511..aa4ca19a 100644 --- a/crates/toolpath-convo/src/project.rs +++ b/crates/toolpath-convo/src/project.rs @@ -164,6 +164,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -364,6 +365,7 @@ mod tests { }, ], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -422,6 +424,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: Some(TokenUsage { input_tokens: Some(100), @@ -445,6 +448,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: Some(TokenUsage { input_tokens: Some(200), diff --git a/crates/toolpath-copilot/src/project.rs b/crates/toolpath-copilot/src/project.rs index ffd8b654..a268b5e3 100644 --- a/crates/toolpath-copilot/src/project.rs +++ b/crates/toolpath-copilot/src/project.rs @@ -784,6 +784,7 @@ mod tests { category: Some(ToolCategory::Shell), }], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -838,6 +839,7 @@ mod tests { thinking: None, tool_uses: vec![tool], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -989,6 +991,7 @@ mod tests { ), ], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -1054,6 +1057,7 @@ mod tests { category: Some(ToolCategory::Shell), }], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, diff --git a/crates/toolpath-copilot/src/provider.rs b/crates/toolpath-copilot/src/provider.rs index 430ef668..3127009c 100644 --- a/crates/toolpath-copilot/src/provider.rs +++ b/crates/toolpath-copilot/src/provider.rs @@ -393,6 +393,7 @@ fn empty_turn(id: String, role: Role, timestamp: String) -> Turn { thinking: None, tool_uses: Vec::new(), model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, diff --git a/crates/toolpath-cursor/examples/dump_fixture.rs b/crates/toolpath-cursor/examples/dump_fixture.rs index 6fc8ee73..97ead68b 100644 --- a/crates/toolpath-cursor/examples/dump_fixture.rs +++ b/crates/toolpath-cursor/examples/dump_fixture.rs @@ -281,6 +281,7 @@ fn view_from_jsonl( thinking: None, tool_uses, model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, diff --git a/crates/toolpath-cursor/src/project.rs b/crates/toolpath-cursor/src/project.rs index 97934fba..7b6d7962 100644 --- a/crates/toolpath-cursor/src/project.rs +++ b/crates/toolpath-cursor/src/project.rs @@ -919,6 +919,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -943,6 +944,7 @@ mod tests { thinking: None, tool_uses: vec![], model: Some("claude-opus-4-7".into()), + harness_generated: false, stop_reason: None, token_usage: Some(TokenUsage { input_tokens: Some(10), diff --git a/crates/toolpath-cursor/src/provider.rs b/crates/toolpath-cursor/src/provider.rs index d1276f39..6edd26f5 100644 --- a/crates/toolpath-cursor/src/provider.rs +++ b/crates/toolpath-cursor/src/provider.rs @@ -374,6 +374,7 @@ impl<'a> Builder<'a> { thinking: None, tool_uses: Vec::new(), model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -465,6 +466,7 @@ impl<'a> Builder<'a> { thinking, tool_uses, model, + harness_generated: false, stop_reason: None, token_usage, attributed_token_usage: None, diff --git a/crates/toolpath-cursor/tests/projection_roundtrip.rs b/crates/toolpath-cursor/tests/projection_roundtrip.rs index c4317a0a..f276a598 100644 --- a/crates/toolpath-cursor/tests/projection_roundtrip.rs +++ b/crates/toolpath-cursor/tests/projection_roundtrip.rs @@ -240,6 +240,7 @@ fn projector_accepts_foreign_view_shape() { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -269,6 +270,7 @@ fn projector_accepts_foreign_view_shape() { category: Some(ToolCategory::FileWrite), }], model: Some("claude-opus-4-7".into()), + harness_generated: false, stop_reason: Some("end_turn".into()), token_usage: Some(TokenUsage { input_tokens: Some(20), diff --git a/crates/toolpath-gemini/src/project.rs b/crates/toolpath-gemini/src/project.rs index 5d487f3f..cea06762 100644 --- a/crates/toolpath-gemini/src/project.rs +++ b/crates/toolpath-gemini/src/project.rs @@ -606,6 +606,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -626,6 +627,7 @@ mod tests { thinking: None, tool_uses: vec![], model: Some("gemini-3-flash-preview".into()), + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, diff --git a/crates/toolpath-gemini/src/provider.rs b/crates/toolpath-gemini/src/provider.rs index ead4700a..c6c604a5 100644 --- a/crates/toolpath-gemini/src/provider.rs +++ b/crates/toolpath-gemini/src/provider.rs @@ -121,6 +121,7 @@ fn message_to_turn(msg: &GeminiMessage, working_dir: Option<&str>) -> Turn { thinking, tool_uses, model: msg.model.clone(), + harness_generated: false, stop_reason: None, token_usage, attributed_token_usage: None, diff --git a/crates/toolpath-opencode/src/project.rs b/crates/toolpath-opencode/src/project.rs index 0f34ae45..476e9031 100644 --- a/crates/toolpath-opencode/src/project.rs +++ b/crates/toolpath-opencode/src/project.rs @@ -761,6 +761,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -781,6 +782,7 @@ mod tests { thinking: None, tool_uses: vec![], model: Some("claude-sonnet-4-6".into()), + harness_generated: false, stop_reason: Some("stop".into()), token_usage: None, attributed_token_usage: None, diff --git a/crates/toolpath-opencode/src/provider.rs b/crates/toolpath-opencode/src/provider.rs index 9f05349f..de6039ba 100644 --- a/crates/toolpath-opencode/src/provider.rs +++ b/crates/toolpath-opencode/src/provider.rs @@ -293,6 +293,7 @@ impl<'a> Builder<'a> { thinking: None, tool_uses: Vec::new(), model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -470,6 +471,7 @@ impl<'a> Builder<'a> { } else { Some(a.model_id.clone()) }, + harness_generated: false, stop_reason: stop_reason.or_else(|| a.finish.clone()), token_usage, attributed_token_usage: None, diff --git a/crates/toolpath-pi/src/project.rs b/crates/toolpath-pi/src/project.rs index a82b15d1..bb3cebba 100644 --- a/crates/toolpath-pi/src/project.rs +++ b/crates/toolpath-pi/src/project.rs @@ -767,6 +767,7 @@ mod tests { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -787,6 +788,7 @@ mod tests { thinking: None, tool_uses: vec![], model: Some("claude-sonnet-4-5".into()), + harness_generated: false, stop_reason: Some("stop".into()), token_usage: Some(TokenUsage { input_tokens: Some(100), diff --git a/crates/toolpath-pi/src/provider.rs b/crates/toolpath-pi/src/provider.rs index f536b952..b5248551 100644 --- a/crates/toolpath-pi/src/provider.rs +++ b/crates/toolpath-pi/src/provider.rs @@ -261,6 +261,7 @@ pub fn session_to_view(session: &PiSession) -> ConversationView { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -281,6 +282,7 @@ pub fn session_to_view(session: &PiSession) -> ConversationView { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -301,6 +303,7 @@ pub fn session_to_view(session: &PiSession) -> ConversationView { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -326,6 +329,7 @@ pub fn session_to_view(session: &PiSession) -> ConversationView { thinking: None, tool_uses: vec![], model: None, + harness_generated: false, stop_reason: None, token_usage: None, attributed_token_usage: None, @@ -466,6 +470,7 @@ pub fn session_to_view(session: &PiSession) -> ConversationView { thinking, tool_uses, model, + harness_generated: false, stop_reason: stop_reason_s, token_usage, attributed_token_usage: None, diff --git a/site/_data/crates.json b/site/_data/crates.json index 4d282a97..28077ef6 100644 --- a/site/_data/crates.json +++ b/site/_data/crates.json @@ -9,7 +9,7 @@ }, { "name": "toolpath-convo", - "version": "0.11.1", + "version": "0.12.0", "description": "Provider-agnostic conversation types, traits, and Toolpath-Path derivation", "docs": "https://docs.rs/toolpath-convo", "crate": "https://crates.io/crates/toolpath-convo", @@ -33,7 +33,7 @@ }, { "name": "toolpath-claude", - "version": "0.12.2", + "version": "0.12.3", "description": "Derive from Claude conversation logs", "docs": "https://docs.rs/toolpath-claude", "crate": "https://crates.io/crates/toolpath-claude",