Skip to content
Open

Codex #229

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1f00414
hooks: Add Codex session and model identity helpers
davidabram Aug 22, 2026
8d2ba56
config: Add Codex skill generation target
davidabram Aug 22, 2026
940a697
config: Generate Codex hook assets
davidabram Aug 22, 2026
788a8bd
setup: Add Codex as a configuration install target
davidabram Aug 22, 2026
2138559
hooks: Implement Codex dispatcher skeleton
davidabram Aug 22, 2026
0fb352c
hooks: Capture Codex user prompts in Agent Trace
davidabram Aug 22, 2026
1e95b87
hooks: Capture Codex Stop conversation evidence
davidabram Aug 22, 2026
ccedd94
hooks: Route Codex Bash hooks through the shared policy engine
davidabram Aug 22, 2026
06a06c5
doctor: Add Codex integration health coverage
davidabram Aug 22, 2026
3ada88f
codex: Implement PostToolUse apply_patch tracing
davidabram Aug 22, 2026
cb49faa
runtime: Harden Codex hook integration and apply_patch attribution
davidabram Aug 22, 2026
dd99dfb
hooks: Accept safe Codex apply_patch path traversal
davidabram Aug 22, 2026
aa6259d
setup+doctor: Add non-destructive Codex hook configuration merging
davidabram Aug 23, 2026
fa80af7
doctor: Add structural and trust-aware Codex hook diagnosis
davidabram Aug 23, 2026
39f48e8
config: Parameterize generated skill input references for Codex
davidabram Aug 23, 2026
4c86232
hooks: Handle nullable Codex stop events and identifier validation
davidabram Aug 23, 2026
4e0b246
hooks: Validate Codex Stop/UserPromptSubmit identifiers before side e…
davidabram Aug 23, 2026
31c371d
agent-trace: Persist Codex conversation events atomically
davidabram Aug 23, 2026
5b93350
db: Clarify duplicate insert transaction semantics
davidabram Aug 23, 2026
33224d3
hooks: Defer Codex apply_patch path safety to resolution
davidabram Aug 23, 2026
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
60 changes: 53 additions & 7 deletions cli/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.11"
tokio = { version = "1", default-features = false, features = ["rt", "io-util", "sync", "time"] }
toml = "0.9"
tracing = "0.1"

uuid = { version = "1", features = ["v4", "v7"] }
Expand Down
45 changes: 44 additions & 1 deletion cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,43 @@ const TARGETS: &[TargetSpec] = &[
TargetSpec {
const_name: "OPENCODE_EMBEDDED_ASSETS",
generated_root: "config/.opencode",
allow_dead_code: false,
},
TargetSpec {
const_name: "CLAUDE_EMBEDDED_ASSETS",
generated_root: "config/.claude",
allow_dead_code: false,
},
TargetSpec {
const_name: "PI_EMBEDDED_ASSETS",
generated_root: "config/.pi",
allow_dead_code: false,
},
TargetSpec {
const_name: "CODEX_EMBEDDED_ASSETS",
generated_root: CODEX_TARGET_DIR,
// Not wired into any SetupTarget/install path yet (T05); only read by
// this task's own test so far.
allow_dead_code: true,
},
TargetSpec {
const_name: "HOOK_EMBEDDED_ASSETS",
generated_root: "static/hooks",
allow_dead_code: false,
},
];

/// Build-time-only staging directory (inside `OUT_DIR`) that merges Codex's two
/// Pkl-generated output roots, `config/.agents` and `config/.codex`, into one
/// tree so it can be embedded like every other single-root target below.
const CODEX_TARGET_DIR: &str = "config/codex-target";
const CODEX_AGENTS_SOURCE_DIR: &str = "config/.agents";
const CODEX_HOOKS_SOURCE_DIR: &str = "config/.codex";

struct TargetSpec {
const_name: &'static str,
generated_root: &'static str,
allow_dead_code: bool,
}

fn main() {
Expand Down Expand Up @@ -84,6 +103,7 @@ fn prepare_build_artifacts() -> io::Result<()> {
} else {
stage_packaged_fallback(&manifest_dir, &out_dir)?;
}
stage_codex_target(&out_dir)?;
validate_staged_artifacts(&out_dir)?;
generate_embedded_asset_manifest(&out_dir)?;
generate_optional_workflow_catalog(&out_dir)?;
Expand Down Expand Up @@ -281,7 +301,7 @@ fn validate_fallback_inventory(fallback_root: &Path) -> io::Result<()> {
}

fn validate_staged_artifacts(out_dir: &Path) -> io::Result<()> {
for target in TARGETS.iter().take(3) {
for target in TARGETS.iter().take(4) {
let expected_root = out_dir.join(PKL_OUTPUT_DIR).join(target.generated_root);
if !expected_root.is_dir() {
return Err(invalid_data(&format!(
Expand Down Expand Up @@ -331,6 +351,26 @@ fn stage_static_inputs(
)
}

/// Merges Codex's two Pkl-generated output roots into `CODEX_TARGET_DIR` so it
/// can be embedded through the same single-root `TargetSpec` mechanism as every
/// other target. Runs after both the repository-source and packaged-fallback
/// staging branches, since either one populates the generated payload this
/// reads from.
fn stage_codex_target(out_dir: &Path) -> io::Result<()> {
let pkl_output_root = out_dir.join(PKL_OUTPUT_DIR);
let destination_root = pkl_output_root.join(CODEX_TARGET_DIR);
remove_path_if_exists(&destination_root)?;

copy_tree(
&pkl_output_root.join(CODEX_AGENTS_SOURCE_DIR),
&destination_root.join(".agents"),
)?;
copy_tree(
&pkl_output_root.join(CODEX_HOOKS_SOURCE_DIR),
&destination_root.join(".codex"),
)
}

fn copy_tree(source_root: &Path, destination_root: &Path) -> io::Result<()> {
println!("cargo:rerun-if-changed={}", source_root.display());

Expand Down Expand Up @@ -389,6 +429,9 @@ fn generate_embedded_asset_manifest(out_dir: &Path) -> io::Result<()> {
collect_files(&source_root, &source_root, &mut files)?;
files.sort_unstable_by(|left, right| left.relative_path.cmp(&right.relative_path));

if target.allow_dead_code {
output.push_str("#[allow(dead_code)]\n");
}
writeln!(
output,
"pub static {}: &[EmbeddedAsset] = &[",
Expand Down
14 changes: 10 additions & 4 deletions cli/src/cli_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,16 +168,19 @@ pub enum Commands {

#[command(about = SETUP_CLAP_ABOUT, hide = !SETUP_SHOW_IN_TOP_LEVEL_HELP)]
Setup {
#[arg(long, conflicts_with_all = ["claude", "pi", "all"])]
#[arg(long, conflicts_with_all = ["claude", "pi", "codex", "all"])]
opencode: bool,

#[arg(long, conflicts_with_all = ["opencode", "pi", "all"])]
#[arg(long, conflicts_with_all = ["opencode", "pi", "codex", "all"])]
claude: bool,

#[arg(long, conflicts_with_all = ["opencode", "claude", "all"])]
#[arg(long, conflicts_with_all = ["opencode", "claude", "codex", "all"])]
pi: bool,

#[arg(long, conflicts_with_all = ["opencode", "claude", "pi"])]
#[arg(long, conflicts_with_all = ["opencode", "claude", "pi", "all"])]
codex: bool,

#[arg(long, conflicts_with_all = ["opencode", "claude", "pi", "codex"])]
all: bool,

#[arg(long)]
Expand Down Expand Up @@ -317,6 +320,9 @@ pub enum HooksSubcommand {

#[command(about = "Run conversation-trace hook (reads JSON payload from STDIN)")]
ConversationTrace,

#[command(about = "Run Codex hook (reads JSON payload from STDIN)")]
Codex,
}

#[derive(Subcommand, Debug, Clone, PartialEq, Eq)]
Expand Down
2 changes: 1 addition & 1 deletion cli/src/command_surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const HELP_SECTIONS: &[HelpSection] = &[
body: &[HelpSectionBodyLine::Command {
cmd: " sce setup",
suffix:
" [--opencode|--claude|--pi|--all] [--non-interactive] [--hooks] [--repo <path>] [--bootstrap-context]",
" [--opencode|--claude|--pi|--codex|--all] [--non-interactive] [--hooks] [--repo <path>] [--bootstrap-context]",
}],
},
HelpSection {
Expand Down
49 changes: 49 additions & 0 deletions cli/src/services/agent_trace_db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ pub const INSERT_PART_SQL: &str =
"INSERT INTO parts (type, text, message_id, session_id, generated_at_unix_ms)
VALUES (?1, ?2, ?3, ?4, ?5)";

/// Parameterized SQL for checking whether a message row already exists,
/// used as the existence guard for
/// [`insert_conversation_text_event_with`].
const SELECT_MESSAGE_EXISTS_SQL: &str =
"SELECT 1 FROM messages WHERE session_id = ?1 AND message_id = ?2 LIMIT 1";

const CONVERSATION_TEXT_EVENT_OPERATION_NAME: &str = "insert conversation text event";
const CONVERSATION_TEXT_EVENT_RETRY_HINT: &str = "retry after the database lock clears; if the issue persists, stop other SCE processes using this database and rerun the command";

/// Diff trace payload to persist in the agent trace database.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DiffTraceInsert<'a> {
Expand Down Expand Up @@ -330,6 +339,46 @@ fn insert_parts_with<M: DbSpec>(db: &TursoDb<M>, inputs: Vec<InsertPartInsert>)
db.execute(&sql, params)
}

/// Atomically insert one conversation `messages` row and its one `parts`
/// row: if `(message.session_id, message.message_id)` already exists, this
/// is a no-op (`Ok(false)`); otherwise both rows insert together in one
/// transaction (`Ok(true)`). `fail_before_part_insert` is a test-only hook
/// forcing the transaction to fail after the message insert and before the
/// part insert, to prove both roll back together.
fn insert_conversation_text_event_with<M: DbSpec>(
db: &TursoDb<M>,
message: InsertMessageInsert,
part: InsertPartInsert,
fail_before_part_insert: bool,
) -> Result<bool> {
let exists_params = (message.session_id.clone(), message.message_id.clone());
let message_params = (
message.session_id,
message.message_id,
message.role.to_string(),
message.generated_at_unix_ms,
);
let part_params = (
part.part_type.to_string(),
part.text,
part.message_id,
part.session_id,
part.generated_at_unix_ms,
);

db.execute_transactional_insert_pair_if_absent(
CONVERSATION_TEXT_EVENT_OPERATION_NAME,
CONVERSATION_TEXT_EVENT_RETRY_HINT,
SELECT_MESSAGE_EXISTS_SQL,
exists_params,
INSERT_MESSAGE_SQL,
message_params,
INSERT_PART_SQL,
part_params,
fail_before_part_insert,
)
}

fn numbered_placeholders(start: usize, count: usize) -> String {
let placeholders = (start..start + count)
.map(|index| format!("?{index}"))
Expand Down
Loading
Loading