Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cli/src/cli_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,13 @@ pub enum HooksSubcommand {
hide = true
)]
CodexMutationScope,

#[command(
name = "opencode-mutation-scope",
about = "Run the OpenCode mutation-scope adapter (reads JSON payload from STDIN)",
hide = true
)]
OpenCodeMutationScope,
}

#[derive(Subcommand, Debug, Clone, PartialEq, Eq)]
Expand Down
36 changes: 36 additions & 0 deletions cli/src/services/hooks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub mod codex_mutation_scope;
pub mod command;
pub mod lifecycle;
pub mod mutation_scope;
pub mod opencode_mutation_scope;

pub const NAME: &str = "hooks";
pub const CANONICAL_SCE_COAUTHOR_TRAILER: &str = "Co-authored-by: SCE <sce@crocoder.dev>";
Expand Down Expand Up @@ -106,6 +107,7 @@ pub enum HookSubcommand {
MutationScope,
ClaudeMutationScope,
CodexMutationScope,
OpenCodeMutationScope,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
Expand Down Expand Up @@ -247,6 +249,9 @@ fn run_hooks_subcommand_in_repo(
HookSubcommand::CodexMutationScope => {
codex_mutation_scope::run_codex_mutation_scope_subcommand(logger)
}
HookSubcommand::OpenCodeMutationScope => {
opencode_mutation_scope::run_opencode_mutation_scope_subcommand(logger)
}
}
}

Expand Down Expand Up @@ -1091,6 +1096,15 @@ fn normalize_codex_model_id(model: &str) -> Option<String> {
Some(normalized.to_string())
}

fn normalize_opencode_model_id(model: &str) -> Option<String> {
let normalized = model.trim();
if normalized.is_empty() {
return None;
}

Some(normalized.to_string())
}

fn extract_claude_event_time(payload: &serde_json::Map<String, Value>) -> u64 {
for key in &["time", "timestamp"] {
if let Some(time_value) = payload.get(*key) {
Expand Down Expand Up @@ -1933,6 +1947,7 @@ fn hook_runtime_invocation_name(subcommand: &HookSubcommand) -> &'static str {
HookSubcommand::MutationScope => "mutation-scope runtime invocation",
HookSubcommand::ClaudeMutationScope => "Claude mutation-scope runtime invocation",
HookSubcommand::CodexMutationScope => "Codex mutation-scope runtime invocation",
HookSubcommand::OpenCodeMutationScope => "OpenCode mutation-scope runtime invocation",
}
}

Expand Down Expand Up @@ -3270,6 +3285,27 @@ mod tests {
assert_eq!(normalize_codex_model_id(" "), None);
}

#[test]
fn normalize_opencode_model_id_preserves_qualified_and_unqualified_ids() {
for model in [
"opencode/big-pickle",
"anthropic/claude-sonnet-4",
"custom-model",
] {
assert_eq!(normalize_opencode_model_id(model).as_deref(), Some(model));
}
assert_eq!(
normalize_opencode_model_id(" opencode/big-pickle ").as_deref(),
Some("opencode/big-pickle")
);
}

#[test]
fn normalize_opencode_model_id_returns_none_for_blank_model_ids() {
assert_eq!(normalize_opencode_model_id(""), None);
assert_eq!(normalize_opencode_model_id(" "), None);
}

#[test]
fn pi_normalized_diff_trace_payload_persists_with_pi_prefixed_session_id() {
let stdin_payload = serde_json::json!({
Expand Down
761 changes: 761 additions & 0 deletions cli/src/services/hooks/opencode_mutation_scope/fixtures/NOTES.md

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{"tag":"bash-perm-ask","plugin":"order-first","wall":"2026-09-10T13:16:46.186Z","pid":177384,"kind":"plugin.init"}
{"seq":1,"tag":"bash-perm-ask","plugin":"capture","mono_us":571,"wall":"2026-09-10T13:16:46.186Z","pid":177384,"kind":"plugin.init","directory":"<PROBE_REPO>","worktree":"<PROBE_REPO>"}
{"tag":"bash-perm-ask","plugin":"order-last","wall":"2026-09-10T13:16:46.186Z","pid":177384,"kind":"plugin.init"}
{"seq":2,"tag":"bash-perm-ask","plugin":"capture","mono_us":805,"wall":"2026-09-10T13:16:46.186Z","pid":177384,"kind":"hook","hook":"config","pluginList":["file://<PROBE_REPO>/.opencode/probe/order-first.ts","file://<PROBE_REPO>/.opencode/probe/capture.ts","file://<PROBE_REPO>/.opencode/probe/order-last.ts"]}
{"seq":3,"tag":"bash-perm-ask","plugin":"capture","mono_us":48843,"wall":"2026-09-10T13:16:46.234Z","pid":177384,"kind":"event","type":"session.created","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","info":{"id":"ses_f748950e8ffeLG65jZ7iGwNoWd","slug":"neon-river","version":"1.15.4","projectID":"9769d8b66ec0cb2cd84ca06681af0216e6bf8f6a","directory":"<PROBE_REPO>","path":"","title":"New session - 2026-09-10T13:16:46.231Z","permission":[{"permission":"question","pattern":"*","action":"deny"},{"permission":"plan_enter","pattern":"*","action":"deny"},{"permission":"plan_exit","pattern":"*","action":"deny"}],"cost":0,"tokens":{"input":0,"output":0,"reasoning":0,"cache":{"read":0,"write":0}},"time":{"created":1789046206231,"updated":1789046206231}}}}
{"seq":4,"tag":"bash-perm-ask","plugin":"capture","mono_us":50629,"wall":"2026-09-10T13:16:46.236Z","pid":177384,"kind":"event","type":"session.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","info":{"id":"ses_f748950e8ffeLG65jZ7iGwNoWd","slug":"neon-river","version":"1.15.4","projectID":"9769d8b66ec0cb2cd84ca06681af0216e6bf8f6a","directory":"<PROBE_REPO>","path":"","title":"New session - 2026-09-10T13:16:46.231Z","permission":[{"permission":"question","pattern":"*","action":"deny"},{"permission":"plan_enter","pattern":"*","action":"deny"},{"permission":"plan_exit","pattern":"*","action":"deny"}],"cost":0,"tokens":{"input":0,"output":0,"reasoning":0,"cache":{"read":0,"write":0}},"time":{"created":1789046206231,"updated":1789046206231}}}}
{"seq":5,"tag":"bash-perm-ask","plugin":"capture","mono_us":85730,"wall":"2026-09-10T13:16:46.271Z","pid":177384,"kind":"event","type":"session.next.agent.switched","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","timestamp":"2026-09-10T13:16:46.268Z","agent":"build"}}
{"seq":6,"tag":"bash-perm-ask","plugin":"capture","mono_us":87541,"wall":"2026-09-10T13:16:46.273Z","pid":177384,"kind":"event","type":"session.next.model.switched","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","timestamp":"2026-09-10T13:16:46.268Z","model":{"id":"big-pickle","providerID":"opencode","variant":"default"}}}
{"seq":7,"tag":"bash-perm-ask","plugin":"capture","mono_us":88980,"wall":"2026-09-10T13:16:46.274Z","pid":177384,"kind":"hook","hook":"chat.message","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","model":{"providerID":"opencode","modelID":"big-pickle"}}
{"seq":8,"tag":"bash-perm-ask","plugin":"capture","mono_us":93090,"wall":"2026-09-10T13:16:46.278Z","pid":177384,"kind":"event","type":"message.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","info":{"id":"msg_08b76af3c001qiHQfE61Ji1zyU","role":"user","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","time":{"created":1789046206268},"agent":"build","model":{"providerID":"opencode","modelID":"big-pickle"}}}}
{"seq":9,"tag":"bash-perm-ask","plugin":"capture","mono_us":94451,"wall":"2026-09-10T13:16:46.280Z","pid":177384,"kind":"event","type":"message.part.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","part":{"type":"text","text":"\"Use the bash tool exactly once to run: echo x > s6.txt\"","messageID":"msg_08b76af3c001qiHQfE61Ji1zyU","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","id":"prt_08b76af42001D8r0lYIcTO0Pt8"},"time":1789046206279}}
{"seq":10,"tag":"bash-perm-ask","plugin":"capture","mono_us":97081,"wall":"2026-09-10T13:16:46.282Z","pid":177384,"kind":"event","type":"session.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","info":{"id":"ses_f748950e8ffeLG65jZ7iGwNoWd","slug":"neon-river","projectID":"9769d8b66ec0cb2cd84ca06681af0216e6bf8f6a","directory":"<PROBE_REPO>","path":"","title":"New session - 2026-09-10T13:16:46.231Z","agent":"build","model":{"id":"big-pickle","providerID":"opencode","variant":"default"},"version":"1.15.4","cost":0,"tokens":{"input":0,"output":0,"reasoning":0,"cache":{"read":0,"write":0}},"permission":[{"permission":"question","pattern":"*","action":"deny"},{"permission":"plan_enter","pattern":"*","action":"deny"},{"permission":"plan_exit","pattern":"*","action":"deny"}],"time":{"created":1789046206231,"updated":1789046206280}}}}
{"seq":11,"tag":"bash-perm-ask","plugin":"capture","mono_us":206230,"wall":"2026-09-10T13:16:46.391Z","pid":177384,"kind":"event","type":"session.status","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","status":{"type":"busy"}}}
{"seq":12,"tag":"bash-perm-ask","plugin":"capture","mono_us":231944,"wall":"2026-09-10T13:16:46.417Z","pid":177384,"kind":"event","type":"message.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","info":{"id":"msg_08b76afd0001mPlJhfKe2ODFxt","parentID":"msg_08b76af3c001qiHQfE61Ji1zyU","role":"assistant","mode":"build","agent":"build","path":{"cwd":"<PROBE_REPO>","root":"<PROBE_REPO>"},"cost":0,"tokens":{"input":0,"output":0,"reasoning":0,"cache":{"read":0,"write":0}},"modelID":"big-pickle","providerID":"opencode","time":{"created":1789046206416},"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd"}}}
{"seq":13,"tag":"bash-perm-ask","plugin":"capture","mono_us":238517,"wall":"2026-09-10T13:16:46.424Z","pid":177384,"kind":"hook","hook":"chat.params","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","agent":"title","model_id":"big-pickle","model_providerID":"opencode","model_api_id":"big-pickle","provider_source":"custom","message_id":"msg_08b76af3c001qiHQfE61Ji1zyU"}
{"seq":14,"tag":"bash-perm-ask","plugin":"capture","mono_us":289870,"wall":"2026-09-10T13:16:46.475Z","pid":177384,"kind":"event","type":"session.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","info":{"id":"ses_f748950e8ffeLG65jZ7iGwNoWd","slug":"neon-river","projectID":"9769d8b66ec0cb2cd84ca06681af0216e6bf8f6a","directory":"<PROBE_REPO>","path":"","title":"New session - 2026-09-10T13:16:46.231Z","agent":"build","model":{"id":"big-pickle","providerID":"opencode","variant":"default"},"version":"1.15.4","summary":{"additions":0,"deletions":0,"files":0},"cost":0,"tokens":{"input":0,"output":0,"reasoning":0,"cache":{"read":0,"write":0}},"permission":[{"permission":"question","pattern":"*","action":"deny"},{"permission":"plan_enter","pattern":"*","action":"deny"},{"permission":"plan_exit","pattern":"*","action":"deny"}],"time":{"created":1789046206231,"updated":1789046206473}}}}
{"seq":15,"tag":"bash-perm-ask","plugin":"capture","mono_us":297482,"wall":"2026-09-10T13:16:46.483Z","pid":177384,"kind":"event","type":"session.diff","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","diff":[]}}
{"seq":16,"tag":"bash-perm-ask","plugin":"capture","mono_us":298629,"wall":"2026-09-10T13:16:46.484Z","pid":177384,"kind":"event","type":"message.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","info":{"role":"user","time":{"created":1789046206268},"agent":"build","model":{"providerID":"opencode","modelID":"big-pickle"},"id":"msg_08b76af3c001qiHQfE61Ji1zyU","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","summary":{"diffs":[]}}}}
{"seq":17,"tag":"bash-perm-ask","plugin":"capture","mono_us":299183,"wall":"2026-09-10T13:16:46.484Z","pid":177384,"kind":"hook","hook":"chat.params","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","agent":"build","model_id":"big-pickle","model_providerID":"opencode","model_api_id":"big-pickle","provider_source":"custom","message_id":"msg_08b76af3c001qiHQfE61Ji1zyU"}
{"seq":18,"tag":"bash-perm-ask","plugin":"capture","mono_us":301726,"wall":"2026-09-10T13:16:46.487Z","pid":177384,"kind":"event","type":"session.status","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","status":{"type":"busy"}}}
{"seq":19,"tag":"bash-perm-ask","plugin":"capture","mono_us":1834859,"wall":"2026-09-10T13:16:48.020Z","pid":177384,"kind":"event","type":"message.part.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","part":{"id":"prt_08b76b612001aXsnGDxyqVzDQM","messageID":"msg_08b76afd0001mPlJhfKe2ODFxt","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","snapshot":"de5d5f1175516e3735e02b50346d209f22ef3d59","type":"step-start"},"time":1789046208019}}
{"seq":20,"tag":"bash-perm-ask","plugin":"capture","mono_us":1872918,"wall":"2026-09-10T13:16:48.058Z","pid":177384,"kind":"event","type":"message.part.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","part":{"id":"prt_08b76b6390013z2wg6a8ruf33p","messageID":"msg_08b76afd0001mPlJhfKe2ODFxt","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","type":"reasoning","text":"","time":{"start":1789046208057}},"time":1789046208057}}
{"seq":24,"tag":"bash-perm-ask","plugin":"capture","mono_us":1989096,"wall":"2026-09-10T13:16:48.174Z","pid":177384,"kind":"event","type":"message.part.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","part":{"id":"prt_08b76b6390013z2wg6a8ruf33p","messageID":"msg_08b76afd0001mPlJhfKe2ODFxt","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","type":"reasoning","text":"The user wants me to run a specific bash command.","time":{"start":1789046208057,"end":1789046208173}},"time":1789046208173}}
{"seq":25,"tag":"bash-perm-ask","plugin":"capture","mono_us":1990517,"wall":"2026-09-10T13:16:48.176Z","pid":177384,"kind":"event","type":"message.part.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","part":{"id":"prt_08b76b6af001wJRYqWDYHHbJmG","messageID":"msg_08b76afd0001mPlJhfKe2ODFxt","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","type":"tool","tool":"bash","callID":"call_4579fe30cc294599bb6849e0","state":{"status":"pending","input":{},"raw":""}},"time":1789046208175}}
{"tag":"bash-perm-ask","plugin":"order-first","wall":"2026-09-10T13:16:48.340Z","pid":177384,"kind":"hook","hook":"tool.execute.before","tool":"bash","callID":"call_4579fe30cc294599bb6849e0"}
{"seq":26,"tag":"bash-perm-ask","plugin":"capture","mono_us":2155350,"wall":"2026-09-10T13:16:48.341Z","pid":177384,"kind":"hook","hook":"tool.execute.before","input":{"tool":"bash","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","callID":"call_4579fe30cc294599bb6849e0"},"args":{"command":"echo x > s6.txt","description":"Write x to s6.txt"}}
{"tag":"bash-perm-ask","plugin":"order-last","wall":"2026-09-10T13:16:48.341Z","pid":177384,"kind":"hook","hook":"tool.execute.before","tool":"bash","callID":"call_4579fe30cc294599bb6849e0"}
{"seq":27,"tag":"bash-perm-ask","plugin":"capture","mono_us":2159244,"wall":"2026-09-10T13:16:48.344Z","pid":177384,"kind":"event","type":"message.part.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","part":{"type":"tool","tool":"bash","callID":"call_4579fe30cc294599bb6849e0","state":{"status":"running","input":{"command":"echo x > s6.txt","description":"Write x to s6.txt"},"raw":"","time":{"start":1789046208343}},"id":"prt_08b76b6af001wJRYqWDYHHbJmG","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","messageID":"msg_08b76afd0001mPlJhfKe2ODFxt"},"time":1789046208343}}
{"seq":28,"tag":"bash-perm-ask","plugin":"capture","mono_us":2198082,"wall":"2026-09-10T13:16:48.383Z","pid":177384,"kind":"event","type":"permission.asked","properties":{"id":"per_08b76b77f001azE7TJ2KKtiVVy","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","permission":"bash","patterns":["echo x > s6.txt"],"metadata":{},"always":["echo *"],"tool":{"messageID":"msg_08b76afd0001mPlJhfKe2ODFxt","callID":"call_4579fe30cc294599bb6849e0"}}}
{"seq":29,"tag":"bash-perm-ask","plugin":"capture","mono_us":2199434,"wall":"2026-09-10T13:16:48.385Z","pid":177384,"kind":"event","type":"permission.replied","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","requestID":"per_08b76b77f001azE7TJ2KKtiVVy","reply":"reject"}}
{"seq":30,"tag":"bash-perm-ask","plugin":"capture","mono_us":2203446,"wall":"2026-09-10T13:16:48.389Z","pid":177384,"kind":"event","type":"message.part.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","part":{"type":"tool","tool":"bash","callID":"call_4579fe30cc294599bb6849e0","state":{"status":"error","input":{"command":"echo x > s6.txt","description":"Write x to s6.txt"},"error":"The user rejected permission to use this specific tool call.","time":{"start":1789046208343,"end":1789046208388}},"id":"prt_08b76b6af001wJRYqWDYHHbJmG","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","messageID":"msg_08b76afd0001mPlJhfKe2ODFxt"},"time":1789046208388}}
{"seq":31,"tag":"bash-perm-ask","plugin":"capture","mono_us":2219574,"wall":"2026-09-10T13:16:48.405Z","pid":177384,"kind":"event","type":"message.part.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","part":{"id":"prt_08b76b793001V546eIqToG06wP","reason":"tool-calls","snapshot":"de5d5f1175516e3735e02b50346d209f22ef3d59","messageID":"msg_08b76afd0001mPlJhfKe2ODFxt","sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","type":"step-finish","tokens":{"total":8522,"input":21,"output":53,"reasoning":0,"cache":{"write":0,"read":8448}},"cost":0},"time":1789046208403}}
{"seq":32,"tag":"bash-perm-ask","plugin":"capture","mono_us":2220610,"wall":"2026-09-10T13:16:48.406Z","pid":177384,"kind":"event","type":"message.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","info":{"id":"msg_08b76afd0001mPlJhfKe2ODFxt","parentID":"msg_08b76af3c001qiHQfE61Ji1zyU","role":"assistant","mode":"build","agent":"build","path":{"cwd":"<PROBE_REPO>","root":"<PROBE_REPO>"},"cost":0,"tokens":{"total":8522,"input":21,"output":53,"reasoning":0,"cache":{"write":0,"read":8448}},"modelID":"big-pickle","providerID":"opencode","time":{"created":1789046206416},"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","finish":"tool-calls"}}}
{"seq":33,"tag":"bash-perm-ask","plugin":"capture","mono_us":2232969,"wall":"2026-09-10T13:16:48.418Z","pid":177384,"kind":"event","type":"message.updated","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","info":{"id":"msg_08b76afd0001mPlJhfKe2ODFxt","parentID":"msg_08b76af3c001qiHQfE61Ji1zyU","role":"assistant","mode":"build","agent":"build","path":{"cwd":"<PROBE_REPO>","root":"<PROBE_REPO>"},"cost":0,"tokens":{"total":8522,"input":21,"output":53,"reasoning":0,"cache":{"write":0,"read":8448}},"modelID":"big-pickle","providerID":"opencode","time":{"created":1789046206416,"completed":1789046208417},"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","finish":"tool-calls"}}}
{"seq":34,"tag":"bash-perm-ask","plugin":"capture","mono_us":2235196,"wall":"2026-09-10T13:16:48.420Z","pid":177384,"kind":"event","type":"session.status","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd","status":{"type":"idle"}}}
{"seq":35,"tag":"bash-perm-ask","plugin":"capture","mono_us":2235281,"wall":"2026-09-10T13:16:48.421Z","pid":177384,"kind":"event","type":"session.idle","properties":{"sessionID":"ses_f748950e8ffeLG65jZ7iGwNoWd"}}
{"seq":36,"tag":"bash-perm-ask","plugin":"capture","mono_us":2237174,"wall":"2026-09-10T13:16:48.422Z","pid":177384,"kind":"event","type":"server.instance.disposed","properties":{"directory":"<PROBE_REPO>"}}
Loading
Loading