Skip to content

Commit 970e023

Browse files
committed
mason: enable OpenCode tagging surface
1 parent 8f62194 commit 970e023

5 files changed

Lines changed: 279 additions & 15 deletions

File tree

crates/mc-module/src/lib.rs

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,16 +168,28 @@ pub const fn profile_render_epoch(profile: SerializerProfile) -> u32 {
168168
}
169169
}
170170

171-
/// Normalize the request-local Claude Code surface signal once. Every behavior tied to
172-
/// the reduction tool consumes this value rather than maintaining an independent gate.
171+
/// Normalize the request-local Claude Code surface signal once. This remains limited to
172+
/// Claude Code mechanics such as the Thalamus acknowledgement contract and guidance variant.
173173
pub const fn cc_u1_active(profile: Option<SerializerProfile>, tool_present: bool) -> bool {
174174
matches!(profile, Some(SerializerProfile::ClaudeCodeAnthropic)) && tool_present
175175
}
176176

177+
/// Return whether the provider-visible tagging and reduction overlay may be enabled.
178+
/// OpenCode uses the same overlay when its session exposes ctx_reduce.
179+
pub const fn tagging_surface_active(
180+
profile: Option<SerializerProfile>,
181+
tool_present: bool,
182+
) -> bool {
183+
matches!(
184+
profile,
185+
Some(SerializerProfile::ClaudeCodeAnthropic | SerializerProfile::OpencodeAiSdk)
186+
) && tool_present
187+
}
188+
177189
/// The tagger component of the effective render identity. A false request contributes
178190
/// no component, preserving the render identity used before the capability existed.
179-
pub const fn tagger_feature_epoch(cc_u1_active: bool) -> u32 {
180-
if cc_u1_active {
191+
pub const fn tagger_feature_epoch(tagging_surface_active: bool) -> u32 {
192+
if tagging_surface_active {
181193
TAGGER_FEATURE_EPOCH
182194
} else {
183195
0
@@ -4095,7 +4107,7 @@ impl McHandler {
40954107
} else {
40964108
"present"
40974109
};
4098-
let surface = if loaded.meta.cc_u1_active {
4110+
let surface = if loaded.meta.tagging_surface_active || loaded.meta.cc_u1_active {
40994111
"active"
41004112
} else {
41014113
"inactive"
@@ -12727,6 +12739,55 @@ mod tests {
1272712739
assert_eq!(store.load_pending_agent_drops("ses").unwrap(), pending);
1272812740
}
1272912741

12742+
#[tokio::test(flavor = "current_thread")]
12743+
async fn opencode_raw_drop_range_resolves_minted_tags_and_drains_on_next_bust() {
12744+
let producer = Arc::new(ProducerState::default());
12745+
let (handler, store, _dir, _project) = handler_with_store(producer, default_test_config());
12746+
let messages = (1..=25u64)
12747+
.map(|ordinal| {
12748+
let mid = format!("m{ordinal}");
12749+
let text = format!("output {ordinal}");
12750+
ck(&mid, ordinal, &text)
12751+
})
12752+
.collect::<Vec<_>>();
12753+
let mut transform_request = request(messages);
12754+
transform_request["serializer_profile"] = json!("opencode-aisdk");
12755+
transform_request["tool_present"] = json!(true);
12756+
transform_request["serve_native"] = json!(true);
12757+
12758+
let transition = call_transform_request(&handler, transform_request.clone()).await;
12759+
assert_eq!(transition["surface_state"], "transition");
12760+
let tagged = call_transform_request(&handler, transform_request.clone()).await;
12761+
assert_eq!(tagged["surface_state"], "active");
12762+
let tagged_bytes = serde_json::to_string(&tagged["ck_messages"]).unwrap();
12763+
assert!(tagged_bytes.contains("§1§ output 1"));
12764+
assert!(tagged_bytes.contains("§2§ output 2"));
12765+
assert!(tagged_bytes.contains("§3§ output 3"));
12766+
assert_eq!(store.load_tags_for_session("ses").unwrap().len(), 25);
12767+
12768+
let queued = match handler.handle_agent_drops_value(
12769+
7,
12770+
json!({
12771+
"method": "agent_drops.append",
12772+
"session_id": "ses",
12773+
"drop": "1-3",
12774+
"command_id": "opencode-drop-range",
12775+
}),
12776+
) {
12777+
HandlerOutcome::Response(bytes) => serde_json::from_slice::<Value>(&bytes).unwrap(),
12778+
other => panic!("unexpected handler outcome: {other:?}"),
12779+
};
12780+
assert_eq!(queued, json!({ "ok": true, "queued": 3 }));
12781+
12782+
transform_request["render_config"] = json!("cfg1");
12783+
let drained = call_transform_request(&handler, transform_request).await;
12784+
let drained_bytes = serde_json::to_string(&drained["ck_messages"]).unwrap();
12785+
assert!(drained_bytes.contains("[dropped §1§]"));
12786+
assert!(drained_bytes.contains("[dropped §2§]"));
12787+
assert!(drained_bytes.contains("[dropped §3§]"));
12788+
assert!(store.load_pending_agent_drops("ses").unwrap().is_empty());
12789+
}
12790+
1273012791
#[tokio::test(flavor = "current_thread")]
1273112792
async fn ctx_reduce_command_id_survives_transform_drain() {
1273212793
let producer = Arc::new(ProducerState::default());

crates/mc-module/src/transform.rs

Lines changed: 120 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -842,14 +842,19 @@ fn apply_once(
842842
let mutation_exempt_mid =
843843
latest_assistant_mutation_exempt_mid(&req.messages, serializer_profile, req.mid_turn);
844844
let cc_u1_active = crate::cc_u1_active(serializer_profile, req.tool_present);
845+
let tagging_surface_requested =
846+
crate::tagging_surface_active(serializer_profile, req.tool_present);
845847
let transform_snapshot = store.load_transform_snapshot(&req.session_id)?;
846848
let loaded = transform_snapshot.loaded;
847849
let overlay_frontier = transform_snapshot.overlay_frontier;
848-
let surface_transition = serializer_profile == Some(SerializerProfile::ClaudeCodeAnthropic)
849-
&& loaded.meta.cc_u1_active != cc_u1_active;
850+
// Legacy sessions stored the CC latch before the generic surface latch existed.
851+
// Treat that old true value as the generic latch so an upgrade does not repeat a fold.
852+
let persisted_tagging_surface_active =
853+
loaded.meta.tagging_surface_active || loaded.meta.cc_u1_active;
854+
let surface_transition = persisted_tagging_surface_active != tagging_surface_requested;
850855
let surface_state = if surface_transition {
851856
SurfaceState::Transition
852-
} else if cc_u1_active {
857+
} else if tagging_surface_requested {
853858
SurfaceState::Active
854859
} else {
855860
SurfaceState::Inactive
@@ -874,7 +879,7 @@ fn apply_once(
874879
.filter(|epoch| *epoch != 0)
875880
.map(|epoch| format!("mpe{epoch}"))
876881
.unwrap_or_default();
877-
let tagger_feature_epoch = match crate::tagger_feature_epoch(cc_u1_active) {
882+
let tagger_feature_epoch = match crate::tagger_feature_epoch(tagging_surface_requested) {
878883
0 => String::new(),
879884
epoch => format!("tfe{epoch}"),
880885
};
@@ -890,7 +895,7 @@ fn apply_once(
890895
tagger_feature_epoch: tagger_feature_epoch.clone(),
891896
},
892897
);
893-
let tagging_active = cc_u1_active && loaded.meta.cc_u1_active;
898+
let tagging_active = tagging_surface_requested && persisted_tagging_surface_active;
894899
// Previously stored overlay rows may still replay when boundary-lineage validation
895900
// later forces pass-through. Decisions from this request stay in memory until the
896901
// final cache-state compare-and-swap accepts the pass.
@@ -1152,10 +1157,10 @@ fn apply_once(
11521157
|| reconcile_hard_due
11531158
|| emergency_arm_engaged;
11541159
// Profile defaults remain conservative, while the request-local tool signal enables
1155-
// full-array tail reclaim for the Claude Code profile. A false request therefore
1160+
// full-array tail reclaim for an active tagging surface. A false request therefore
11561161
// retains the exact pre-capability behavior without changing the global profile table.
1157-
let tail_reclaim_enabled =
1158-
serializer_profile.is_none_or(|profile| healing::tail_reclaim(profile) || cc_u1_active);
1162+
let tail_reclaim_enabled = serializer_profile
1163+
.is_none_or(|profile| healing::tail_reclaim(profile) || tagging_surface_requested);
11591164
let producer_gate = tail_reclaim_enabled
11601165
&& producer_gate(
11611166
scheduler_outcome.pass,
@@ -1170,7 +1175,7 @@ fn apply_once(
11701175
PassClass::Defer
11711176
};
11721177
let tail_for_selection = tail_sel_items(&live, loaded.meta.coverage_ordinal);
1173-
let mut protected_block_ids = if cc_u1_active {
1178+
let mut protected_block_ids = if tagging_surface_requested {
11741179
newest_active_tag_block_ids(
11751180
&loaded.core,
11761181
&loaded.meta,
@@ -1298,6 +1303,7 @@ fn apply_once(
12981303
}
12991304
apply_ingress_meta(&mut meta, req, &projection, provisional_tail_mid);
13001305
meta.cc_u1_active = cc_u1_active;
1306+
meta.tagging_surface_active = tagging_surface_requested;
13011307
if cc_u1_active {
13021308
meta.last_serializer_profile = req.serializer_profile.clone();
13031309
}
@@ -9136,6 +9142,97 @@ mod tests {
91369142
assert!(s.load_tags_for_session("inert").unwrap().is_empty());
91379143
}
91389144

9145+
#[test]
9146+
fn opencode_tagging_surface_tags_tool_results_and_replays_byte_stably() {
9147+
let dir = tempfile::tempdir().unwrap();
9148+
let s = store(dir.path());
9149+
let messages = vec![
9150+
wire_tool_call("call-1", 1, "call_result-1"),
9151+
wire_tool_result(
9152+
"result-1",
9153+
2,
9154+
json!({ "kind": { "type": "text", "text": "tool output" } }),
9155+
),
9156+
];
9157+
let request = active_opencode_req("opencode-tags", "cfg0", messages);
9158+
9159+
let transition = run(&s, &request, &spine());
9160+
assert_eq!(transition.surface_state, SurfaceState::Transition);
9161+
assert!(!serde_json::to_string(transition.messages())
9162+
.unwrap()
9163+
.contains("§1§"));
9164+
9165+
let active = run(&s, &request, &spine());
9166+
assert_eq!(active.surface_state, SurfaceState::Active);
9167+
let active_bytes = serde_json::to_vec(active.messages()).unwrap();
9168+
assert!(serde_json::to_string(active.messages())
9169+
.unwrap()
9170+
.contains("§1§ tool output"));
9171+
assert_eq!(s.load_tags_for_session("opencode-tags").unwrap().len(), 1);
9172+
9173+
let replay = run(&s, &request, &spine());
9174+
assert_eq!(serde_json::to_vec(replay.messages()).unwrap(), active_bytes);
9175+
}
9176+
9177+
#[test]
9178+
fn opencode_tool_absent_keeps_overlay_bytes_disabled() {
9179+
let dir = tempfile::tempdir().unwrap();
9180+
let s = store(dir.path());
9181+
let request = opencode_req(
9182+
"opencode-inactive",
9183+
"cfg0",
9184+
vec![item("m1", 1, "plain output")],
9185+
);
9186+
9187+
let first = run(&s, &request, &spine());
9188+
let replay = run(&s, &request, &spine());
9189+
assert_eq!(first.surface_state, SurfaceState::Inactive);
9190+
assert_eq!(
9191+
serde_json::to_vec(first.messages()).unwrap(),
9192+
serde_json::to_vec(replay.messages()).unwrap()
9193+
);
9194+
assert!(!serde_json::to_string(first.messages())
9195+
.unwrap()
9196+
.contains("§1§"));
9197+
assert!(s
9198+
.load_tags_for_session("opencode-inactive")
9199+
.unwrap()
9200+
.is_empty());
9201+
}
9202+
9203+
#[test]
9204+
fn opencode_surface_flip_folds_once_before_rendering_tags() {
9205+
let dir = tempfile::tempdir().unwrap();
9206+
let s = store(dir.path());
9207+
let mut request =
9208+
opencode_req("opencode-flip", "cfg0", vec![item("m1", 1, "stable bytes")]);
9209+
9210+
let before = run(&s, &request, &spine());
9211+
let before_config = s.load("opencode-flip").unwrap().meta.last_render_config;
9212+
assert_eq!(before.action, "HARD");
9213+
assert!(!before_config.contains("tfe:"));
9214+
9215+
request.tool_present = true;
9216+
let transition = run(&s, &request, &spine());
9217+
let transitioned_config = s.load("opencode-flip").unwrap().meta.last_render_config;
9218+
assert_eq!(transition.action, "HARD");
9219+
assert_ne!(transitioned_config, before_config);
9220+
assert!(transitioned_config.contains("tfe:4:tfe3"));
9221+
assert!(!serde_json::to_string(transition.messages())
9222+
.unwrap()
9223+
.contains("§1§"));
9224+
9225+
let active = run(&s, &request, &spine());
9226+
assert_ne!(active.action, "HARD");
9227+
assert_eq!(
9228+
s.load("opencode-flip").unwrap().meta.last_render_config,
9229+
transitioned_config
9230+
);
9231+
assert!(serde_json::to_string(active.messages())
9232+
.unwrap()
9233+
.contains("§1§ stable bytes"));
9234+
}
9235+
91399236
#[test]
91409237
fn tagger_flip_hards_before_committed_identity_can_render_tags() {
91419238
let dir = tempfile::tempdir().unwrap();
@@ -11617,6 +11714,20 @@ mod tests {
1161711714
request
1161811715
}
1161911716

11717+
fn opencode_req(session: &str, cfg: &str, messages: Vec<CkIngressMessage>) -> TransformRequest {
11718+
profile_req(SerializerProfile::OpencodeAiSdk, session, cfg, messages)
11719+
}
11720+
11721+
fn active_opencode_req(
11722+
session: &str,
11723+
cfg: &str,
11724+
messages: Vec<CkIngressMessage>,
11725+
) -> TransformRequest {
11726+
let mut request = opencode_req(session, cfg, messages);
11727+
request.tool_present = true;
11728+
request
11729+
}
11730+
1162011731
fn effective_render_config_with_epochs(
1162111732
store: &McStore,
1162211733
cfg: &str,

crates/mc-store/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1381,10 +1381,15 @@ pub struct ModuleMeta {
13811381
/// after OpenCode rebuilds the native message array from its database.
13821382
#[serde(default)]
13831383
pub reasoning_cleared_through_ordinal: u64,
1384-
/// The request-local reduction surface state committed with the rendered identity.
1384+
/// The request-local Claude Code mechanics state committed with the rendered identity.
13851385
/// Missing legacy metadata is false, which preserves the dormant render path.
13861386
#[serde(default)]
13871387
pub cc_u1_active: bool,
1388+
/// The request-local tagging surface latch committed with the rendered identity.
1389+
/// Both the current request and this durable latch must be active before overlay bytes
1390+
/// render, so a transition pass can coordinate one cache-breaking HARD first.
1391+
#[serde(default)]
1392+
pub tagging_surface_active: bool,
13881393
/// Reclaimable-token amount at the last Channel-1 append or suppression reset.
13891394
#[serde(default)]
13901395
pub channel1_last_nudge_undropped: i64,

0 commit comments

Comments
 (0)