Skip to content
Draft
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
6 changes: 6 additions & 0 deletions codex-rs/core/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@
"js_repl": {
"type": "boolean"
},
"js_repl_polling": {
"type": "boolean"
},
"js_repl_tools_only": {
"type": "boolean"
},
Expand Down Expand Up @@ -1611,6 +1614,9 @@
"js_repl": {
"type": "boolean"
},
"js_repl_polling": {
"type": "boolean"
},
"js_repl_tools_only": {
"type": "boolean"
},
Expand Down
12 changes: 12 additions & 0 deletions codex-rs/core/src/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ pub enum Feature {
// Experimental
/// Enable JavaScript REPL tools backed by a persistent Node kernel.
JsRepl,
/// Enable js_repl polling helpers and tool.
JsReplPolling,
/// Only expose js_repl tools directly to the model.
JsReplToolsOnly,
/// Use the single unified PTY-backed exec tool.
Expand Down Expand Up @@ -339,6 +341,10 @@ impl Features {
tracing::warn!("js_repl_tools_only requires js_repl; disabling js_repl_tools_only");
features.disable(Feature::JsReplToolsOnly);
}
if features.enabled(Feature::JsReplPolling) && !features.enabled(Feature::JsRepl) {
tracing::warn!("js_repl_polling requires js_repl; disabling js_repl_polling");
features.disable(Feature::JsReplPolling);
}

features
}
Expand Down Expand Up @@ -454,6 +460,12 @@ pub const FEATURES: &[FeatureSpec] = &[
stage: Stage::UnderDevelopment,
default_enabled: false,
},
FeatureSpec {
id: Feature::JsReplPolling,
key: "js_repl_polling",
stage: Stage::UnderDevelopment,
default_enabled: false,
},
FeatureSpec {
id: Feature::JsReplToolsOnly,
key: "js_repl_tools_only",
Expand Down
24 changes: 24 additions & 0 deletions codex-rs/core/src/project_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ fn render_js_repl_instructions(config: &Config) -> Option<String> {
section.push_str("- Top-level bindings persist across cells. If you hit `SyntaxError: Identifier 'x' has already been declared`, reuse the binding, pick a new name, wrap in `{ ... }` for block scope, or reset the kernel with `js_repl_reset`.\n");
section.push_str("- Top-level static import declarations (for example `import x from \"pkg\"`) are currently unsupported in `js_repl`; use dynamic imports with `await import(\"pkg\")` instead.\n");

if config.features.enabled(Feature::JsReplPolling) {
section.push_str("- Polling mode is session-based: call `js_repl` with first-line pragma `// codex-js-repl: poll=true` to get `exec_id` and `session_id`; provide `session_id=<id>` in later `js_repl` calls to reuse that session state. Omit `session_id` to create a new polling session, and note that unknown `session_id` values fail.\n");
section.push_str("- Use `js_repl_poll` with `exec_id` until `status` is `completed` or `error`; poll responses also include `session_id`.\n");
section.push_str("- Use `js_repl_reset({\"session_id\":\"...\"})` to stop one polling session (including any running exec), or `js_repl_reset({})` to reset all js_repl kernels.\n");
section.push_str("- `js_repl_poll` must not be called before a successful `js_repl` polling submission returns an `exec_id`.\n");
section.push_str("- In polling mode, `timeout_ms` is not supported on `js_repl`; use `js_repl_poll` `yield_time_ms` to control poll wait duration.\n");
section.push_str("- If `js_repl` rejects your payload format, resend raw JS with the pragma; do not retry with JSON, quoted strings, or markdown fences.\n");
}

if config.features.enabled(Feature::JsReplToolsOnly) {
section.push_str("- Do not call tools directly; use `js_repl` + `codex.tool(...)` for all tool calls, including shell commands.\n");
section
Expand Down Expand Up @@ -436,6 +445,21 @@ mod tests {
assert_eq!(res, expected);
}

#[tokio::test]
async fn js_repl_polling_instructions_are_feature_gated() {
let tmp = tempfile::tempdir().expect("tempdir");
let mut cfg = make_config(&tmp, 4096, None).await;
cfg.features
.enable(Feature::JsRepl)
.enable(Feature::JsReplPolling);

let res = get_user_instructions(&cfg, None)
.await
.expect("js_repl instructions expected");
let expected = "## JavaScript REPL (Node)\n- Use `js_repl` for Node-backed JavaScript with top-level await in a persistent kernel.\n- `js_repl` is a freeform/custom tool. Direct `js_repl` calls must send raw JavaScript tool input (optionally with first-line `// codex-js-repl: timeout_ms=15000`). Do not wrap code in JSON (for example `{\"code\":\"...\"}`), quotes, or markdown code fences.\n- Helpers: `codex.tmpDir` and `codex.tool(name, args?)`.\n- `codex.tool` executes a normal tool call and resolves to the raw tool output object. Use it for shell and non-shell tools alike.\n- To share generated images with the model, write a file under `codex.tmpDir`, call `await codex.tool(\"view_image\", { path: \"/absolute/path\" })`, then delete the file.\n- Top-level bindings persist across cells. If you hit `SyntaxError: Identifier 'x' has already been declared`, reuse the binding, pick a new name, wrap in `{ ... }` for block scope, or reset the kernel with `js_repl_reset`.\n- Top-level static import declarations (for example `import x from \"pkg\"`) are currently unsupported in `js_repl`; use dynamic imports with `await import(\"pkg\")` instead.\n- Polling mode is session-based: call `js_repl` with first-line pragma `// codex-js-repl: poll=true` to get `exec_id` and `session_id`; provide `session_id=<id>` in later `js_repl` calls to reuse that session state. Omit `session_id` to create a new polling session, and note that unknown `session_id` values fail.\n- Use `js_repl_poll` with `exec_id` until `status` is `completed` or `error`; poll responses also include `session_id`.\n- Use `js_repl_reset({\"session_id\":\"...\"})` to stop one polling session (including any running exec), or `js_repl_reset({})` to reset all js_repl kernels.\n- `js_repl_poll` must not be called before a successful `js_repl` polling submission returns an `exec_id`.\n- In polling mode, `timeout_ms` is not supported on `js_repl`; use `js_repl_poll` `yield_time_ms` to control poll wait duration.\n- If `js_repl` rejects your payload format, resend raw JS with the pragma; do not retry with JSON, quoted strings, or markdown fences.\n- Avoid direct access to `process.stdout` / `process.stderr` / `process.stdin`; it can corrupt the JSON line protocol. Use `console.log` and `codex.tool(...)`.";
assert_eq!(res, expected);
}

/// When both system instructions *and* a project doc are present the two
/// should be concatenated with the separator.
#[tokio::test]
Expand Down
Loading
Loading