mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Add buffered code-mode exec yields (#34441)
## What changed - Add the experimental `code_mode_buffered_exec` feature. - When enabled, default code-mode `exec` calls without an explicit `yield_time_ms` to 30 seconds instead of 10 seconds. - Report the effective default in the model-visible `exec` tool description. ## Testing - Verify that the generated tool description advertises the 30-second default when the feature is enabled. GitOrigin-RevId: 0eea4d0105137f09050ce31aad18c25c79df6b2a
This commit is contained in:
1 parent
c0cd337766
commit
99efeef650
9 files changed
+92
-18
No files matched your search
@@ -253,10 +253,14 @@ pub fn build_exec_tool_description(
|
|||||||
enabled_tools: &[ToolDefinition],
|
enabled_tools: &[ToolDefinition],
|
||||||
deferred_tools: &[ToolDefinition],
|
deferred_tools: &[ToolDefinition],
|
||||||
namespace_descriptions: &BTreeMap<String, ToolNamespaceDescription>,
|
namespace_descriptions: &BTreeMap<String, ToolNamespaceDescription>,
|
||||||
|
default_exec_yield_time_ms: u64,
|
||||||
code_mode_only: bool,
|
code_mode_only: bool,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut sections = Vec::new();
|
let mut sections = Vec::new();
|
||||||
sections.push(EXEC_DESCRIPTION_TEMPLATE.to_string());
|
sections.push(EXEC_DESCRIPTION_TEMPLATE.replace(
|
||||||
|
"Defaults to 10000 ms.",
|
||||||
|
&format!("Defaults to {default_exec_yield_time_ms} ms."),
|
||||||
|
));
|
||||||
if !deferred_tools.is_empty() {
|
if !deferred_tools.is_empty() {
|
||||||
sections.push(DEFERRED_NESTED_TOOLS_GUIDANCE.to_string());
|
sections.push(DEFERRED_NESTED_TOOLS_GUIDANCE.to_string());
|
||||||
}
|
}
|
||||||
@@ -868,6 +872,7 @@ mod tests {
|
|||||||
}],
|
}],
|
||||||
&[],
|
&[],
|
||||||
&BTreeMap::new(),
|
&BTreeMap::new(),
|
||||||
|
crate::DEFAULT_EXEC_YIELD_TIME_MS,
|
||||||
/*code_mode_only*/ true,
|
/*code_mode_only*/ true,
|
||||||
);
|
);
|
||||||
assert!(description.contains(
|
assert!(description.contains(
|
||||||
@@ -879,8 +884,13 @@ bar"
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exec_description_mentions_timeout_helpers() {
|
fn exec_description_mentions_timeout_helpers() {
|
||||||
let description =
|
let description = build_exec_tool_description(
|
||||||
build_exec_tool_description(&[], &[], &BTreeMap::new(), /*code_mode_only*/ false);
|
&[],
|
||||||
|
&[],
|
||||||
|
&BTreeMap::new(),
|
||||||
|
crate::DEFAULT_EXEC_YIELD_TIME_MS,
|
||||||
|
/*code_mode_only*/ false,
|
||||||
|
);
|
||||||
assert!(description.contains("`audio(audioUrlOrItem:"));
|
assert!(description.contains("`audio(audioUrlOrItem:"));
|
||||||
assert!(description.contains("`setTimeout(callback: () => void, delayMs?: number)`"));
|
assert!(description.contains("`setTimeout(callback: () => void, delayMs?: number)`"));
|
||||||
assert!(description.contains("`clearTimeout(timeoutId?: number)`"));
|
assert!(description.contains("`clearTimeout(timeoutId?: number)`"));
|
||||||
@@ -932,6 +942,7 @@ bar"
|
|||||||
],
|
],
|
||||||
&[],
|
&[],
|
||||||
&namespace_descriptions,
|
&namespace_descriptions,
|
||||||
|
crate::DEFAULT_EXEC_YIELD_TIME_MS,
|
||||||
/*code_mode_only*/ true,
|
/*code_mode_only*/ true,
|
||||||
);
|
);
|
||||||
assert_eq!(description.matches("## mcp__sample").count(), 1);
|
assert_eq!(description.matches("## mcp__sample").count(), 1);
|
||||||
@@ -972,6 +983,7 @@ bar"
|
|||||||
}],
|
}],
|
||||||
&[],
|
&[],
|
||||||
&namespace_descriptions,
|
&namespace_descriptions,
|
||||||
|
crate::DEFAULT_EXEC_YIELD_TIME_MS,
|
||||||
/*code_mode_only*/ true,
|
/*code_mode_only*/ true,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1071,6 +1083,7 @@ bar"
|
|||||||
],
|
],
|
||||||
&[],
|
&[],
|
||||||
&BTreeMap::new(),
|
&BTreeMap::new(),
|
||||||
|
crate::DEFAULT_EXEC_YIELD_TIME_MS,
|
||||||
/*code_mode_only*/ true,
|
/*code_mode_only*/ true,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1106,6 +1119,7 @@ bar"
|
|||||||
&[],
|
&[],
|
||||||
&[deferred_tool],
|
&[deferred_tool],
|
||||||
&BTreeMap::new(),
|
&BTreeMap::new(),
|
||||||
|
crate::DEFAULT_EXEC_YIELD_TIME_MS,
|
||||||
/*code_mode_only*/ true,
|
/*code_mode_only*/ true,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1127,6 +1141,7 @@ bar"
|
|||||||
output_schema: None,
|
output_schema: None,
|
||||||
}],
|
}],
|
||||||
&BTreeMap::new(),
|
&BTreeMap::new(),
|
||||||
|
crate::DEFAULT_EXEC_YIELD_TIME_MS,
|
||||||
/*code_mode_only*/ false,
|
/*code_mode_only*/ false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -458,6 +458,9 @@
|
|||||||
"code_mode": {
|
"code_mode": {
|
||||||
"$ref": "#/definitions/FeatureToml_for_CodeModeConfigToml"
|
"$ref": "#/definitions/FeatureToml_for_CodeModeConfigToml"
|
||||||
},
|
},
|
||||||
|
"code_mode_buffered_exec": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"code_mode_host": {
|
"code_mode_host": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@@ -4896,6 +4899,9 @@
|
|||||||
"code_mode": {
|
"code_mode": {
|
||||||
"$ref": "#/definitions/FeatureToml_for_CodeModeConfigToml"
|
"$ref": "#/definitions/FeatureToml_for_CodeModeConfigToml"
|
||||||
},
|
},
|
||||||
|
"code_mode_buffered_exec": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"code_mode_host": {
|
"code_mode_host": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1137,9 +1137,10 @@ impl Session {
|
|||||||
session_configuration.parent_thread_id,
|
session_configuration.parent_thread_id,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
code_mode_service: crate::tools::code_mode::CodeModeService::new(Arc::clone(
|
code_mode_service: crate::tools::code_mode::CodeModeService::new(
|
||||||
&code_mode_session_provider,
|
Arc::clone(&code_mode_session_provider),
|
||||||
)),
|
&config.features,
|
||||||
|
),
|
||||||
tool_search_handler_cache: Default::default(),
|
tool_search_handler_cache: Default::default(),
|
||||||
turn_environments: Arc::clone(&turn_environments),
|
turn_environments: Arc::clone(&turn_environments),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5385,9 +5385,10 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
|||||||
/*attestation_provider*/ None,
|
/*attestation_provider*/ None,
|
||||||
config.http_client_factory(),
|
config.http_client_factory(),
|
||||||
),
|
),
|
||||||
code_mode_service: crate::tools::code_mode::CodeModeService::new(Arc::new(
|
code_mode_service: crate::tools::code_mode::CodeModeService::new(
|
||||||
codex_code_mode::InProcessCodeModeSessionProvider,
|
Arc::new(codex_code_mode::InProcessCodeModeSessionProvider),
|
||||||
)),
|
&config.features,
|
||||||
|
),
|
||||||
tool_search_handler_cache: Default::default(),
|
tool_search_handler_cache: Default::default(),
|
||||||
turn_environments: Arc::clone(&turn_environments),
|
turn_environments: Arc::clone(&turn_environments),
|
||||||
};
|
};
|
||||||
@@ -7542,9 +7543,10 @@ where
|
|||||||
/*attestation_provider*/ None,
|
/*attestation_provider*/ None,
|
||||||
config.http_client_factory(),
|
config.http_client_factory(),
|
||||||
),
|
),
|
||||||
code_mode_service: crate::tools::code_mode::CodeModeService::new(Arc::new(
|
code_mode_service: crate::tools::code_mode::CodeModeService::new(
|
||||||
codex_code_mode::InProcessCodeModeSessionProvider,
|
Arc::new(codex_code_mode::InProcessCodeModeSessionProvider),
|
||||||
)),
|
&config.features,
|
||||||
|
),
|
||||||
tool_search_handler_cache: Default::default(),
|
tool_search_handler_cache: Default::default(),
|
||||||
turn_environments: Arc::clone(&turn_environments),
|
turn_environments: Arc::clone(&turn_environments),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ pub(crate) fn create_code_mode_tool(
|
|||||||
enabled_tools: &[CodeModeToolDefinition],
|
enabled_tools: &[CodeModeToolDefinition],
|
||||||
deferred_tools: &[CodeModeToolDefinition],
|
deferred_tools: &[CodeModeToolDefinition],
|
||||||
namespace_descriptions: &BTreeMap<String, codex_code_mode::ToolNamespaceDescription>,
|
namespace_descriptions: &BTreeMap<String, codex_code_mode::ToolNamespaceDescription>,
|
||||||
|
default_exec_yield_time_ms: u64,
|
||||||
code_mode_only: bool,
|
code_mode_only: bool,
|
||||||
) -> ToolSpec {
|
) -> ToolSpec {
|
||||||
const CODE_MODE_FREEFORM_GRAMMAR: &str = r#"
|
const CODE_MODE_FREEFORM_GRAMMAR: &str = r#"
|
||||||
@@ -26,6 +27,7 @@ SOURCE: /[\s\S]+/
|
|||||||
enabled_tools,
|
enabled_tools,
|
||||||
deferred_tools,
|
deferred_tools,
|
||||||
namespace_descriptions,
|
namespace_descriptions,
|
||||||
|
default_exec_yield_time_ms,
|
||||||
code_mode_only,
|
code_mode_only,
|
||||||
),
|
),
|
||||||
format: FreeformToolFormat {
|
format: FreeformToolFormat {
|
||||||
@@ -58,6 +60,7 @@ mod tests {
|
|||||||
&enabled_tools,
|
&enabled_tools,
|
||||||
&[],
|
&[],
|
||||||
&BTreeMap::new(),
|
&BTreeMap::new(),
|
||||||
|
codex_code_mode::DEFAULT_EXEC_YIELD_TIME_MS,
|
||||||
/*code_mode_only*/ true,
|
/*code_mode_only*/ true,
|
||||||
),
|
),
|
||||||
ToolSpec::Freeform(FreeformTool {
|
ToolSpec::Freeform(FreeformTool {
|
||||||
@@ -66,6 +69,7 @@ mod tests {
|
|||||||
&enabled_tools,
|
&enabled_tools,
|
||||||
&[],
|
&[],
|
||||||
&BTreeMap::new(),
|
&BTreeMap::new(),
|
||||||
|
codex_code_mode::DEFAULT_EXEC_YIELD_TIME_MS,
|
||||||
/*code_mode_only*/ true,
|
/*code_mode_only*/ true,
|
||||||
),
|
),
|
||||||
format: FreeformToolFormat {
|
format: FreeformToolFormat {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ use codex_code_mode::CodeModeSession;
|
|||||||
use codex_code_mode::CodeModeSessionProvider;
|
use codex_code_mode::CodeModeSessionProvider;
|
||||||
use codex_code_mode::CodeModeToolKind;
|
use codex_code_mode::CodeModeToolKind;
|
||||||
use codex_code_mode::RuntimeResponse;
|
use codex_code_mode::RuntimeResponse;
|
||||||
|
use codex_features::Feature;
|
||||||
|
use codex_features::Features;
|
||||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
@@ -52,6 +54,13 @@ pub(crate) use wait_handler::CodeModeWaitHandler;
|
|||||||
pub(crate) const PUBLIC_TOOL_NAME: &str = codex_code_mode::PUBLIC_TOOL_NAME;
|
pub(crate) const PUBLIC_TOOL_NAME: &str = codex_code_mode::PUBLIC_TOOL_NAME;
|
||||||
pub(crate) const WAIT_TOOL_NAME: &str = codex_code_mode::WAIT_TOOL_NAME;
|
pub(crate) const WAIT_TOOL_NAME: &str = codex_code_mode::WAIT_TOOL_NAME;
|
||||||
pub(crate) const DEFAULT_WAIT_YIELD_TIME_MS: u64 = codex_code_mode::DEFAULT_WAIT_YIELD_TIME_MS;
|
pub(crate) const DEFAULT_WAIT_YIELD_TIME_MS: u64 = codex_code_mode::DEFAULT_WAIT_YIELD_TIME_MS;
|
||||||
|
const BUFFERED_EXEC_YIELD_TIME_MS: u64 = 30_000;
|
||||||
|
|
||||||
|
pub(crate) fn default_exec_yield_time_override_ms(features: &Features) -> Option<u64> {
|
||||||
|
features
|
||||||
|
.enabled(Feature::CodeModeBufferedExec)
|
||||||
|
.then_some(BUFFERED_EXEC_YIELD_TIME_MS)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true for the un-namespaced code-mode `exec` tool.
|
/// Returns true for the un-namespaced code-mode `exec` tool.
|
||||||
pub(crate) fn is_exec_tool_name(tool_name: &ToolName) -> bool {
|
pub(crate) fn is_exec_tool_name(tool_name: &ToolName) -> bool {
|
||||||
@@ -68,16 +77,21 @@ pub(crate) struct CodeModeService {
|
|||||||
session: OnceCell<Arc<dyn CodeModeSession>>,
|
session: OnceCell<Arc<dyn CodeModeSession>>,
|
||||||
session_provider: Arc<dyn CodeModeSessionProvider>,
|
session_provider: Arc<dyn CodeModeSessionProvider>,
|
||||||
dispatch_broker: Arc<CodeModeDispatchBroker>,
|
dispatch_broker: Arc<CodeModeDispatchBroker>,
|
||||||
|
default_exec_yield_time_override_ms: Option<u64>,
|
||||||
shutting_down: AtomicBool,
|
shutting_down: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodeModeService {
|
impl CodeModeService {
|
||||||
pub(crate) fn new(session_provider: Arc<dyn CodeModeSessionProvider>) -> Self {
|
pub(crate) fn new(
|
||||||
|
session_provider: Arc<dyn CodeModeSessionProvider>,
|
||||||
|
features: &Features,
|
||||||
|
) -> Self {
|
||||||
let dispatch_broker = Arc::new(CodeModeDispatchBroker::new());
|
let dispatch_broker = Arc::new(CodeModeDispatchBroker::new());
|
||||||
Self {
|
Self {
|
||||||
session: OnceCell::new(),
|
session: OnceCell::new(),
|
||||||
session_provider,
|
session_provider,
|
||||||
dispatch_broker,
|
dispatch_broker,
|
||||||
|
default_exec_yield_time_override_ms: default_exec_yield_time_override_ms(features),
|
||||||
shutting_down: AtomicBool::new(false),
|
shutting_down: AtomicBool::new(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,8 +102,11 @@ impl CodeModeService {
|
|||||||
|
|
||||||
pub(crate) async fn execute(
|
pub(crate) async fn execute(
|
||||||
&self,
|
&self,
|
||||||
request: codex_code_mode::ExecuteRequest,
|
mut request: codex_code_mode::ExecuteRequest,
|
||||||
) -> Result<codex_code_mode::StartedCell, String> {
|
) -> Result<codex_code_mode::StartedCell, String> {
|
||||||
|
if request.yield_time_ms.is_none() {
|
||||||
|
request.yield_time_ms = self.default_exec_yield_time_override_ms;
|
||||||
|
}
|
||||||
self.session().await?.execute(request).await
|
self.session().await?.execute(request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,6 +388,7 @@ mod tests {
|
|||||||
use codex_code_mode::FunctionCallOutputContentItem as CodeModeOutputContentItem;
|
use codex_code_mode::FunctionCallOutputContentItem as CodeModeOutputContentItem;
|
||||||
use codex_code_mode::ProcessOwnedCodeModeSessionProvider;
|
use codex_code_mode::ProcessOwnedCodeModeSessionProvider;
|
||||||
use codex_code_mode::RuntimeResponse;
|
use codex_code_mode::RuntimeResponse;
|
||||||
|
use codex_features::Features;
|
||||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||||
use codex_tools::ToolName;
|
use codex_tools::ToolName;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@@ -444,11 +462,12 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn missing_process_host_falls_back_to_in_process_session() {
|
async fn missing_process_host_falls_back_to_in_process_session() {
|
||||||
let service = CodeModeService::new(Arc::new(
|
let service = CodeModeService::new(
|
||||||
ProcessOwnedCodeModeSessionProvider::with_host_program(
|
Arc::new(ProcessOwnedCodeModeSessionProvider::with_host_program(
|
||||||
"codex-code-mode-host-does-not-exist".into(),
|
"codex-code-mode-host-does-not-exist".into(),
|
||||||
),
|
)),
|
||||||
));
|
&Features::with_defaults(),
|
||||||
|
);
|
||||||
|
|
||||||
let response = service
|
let response = service
|
||||||
.execute(ExecuteRequest {
|
.execute(ExecuteRequest {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::agent::exceeds_thread_spawn_depth_limit;
|
|||||||
use crate::agent::next_thread_spawn_depth;
|
use crate::agent::next_thread_spawn_depth;
|
||||||
use crate::session::step_context::StepContext;
|
use crate::session::step_context::StepContext;
|
||||||
use crate::session::turn_context::TurnContext;
|
use crate::session::turn_context::TurnContext;
|
||||||
|
use crate::tools::code_mode::default_exec_yield_time_override_ms;
|
||||||
use crate::tools::code_mode::execute_spec::create_code_mode_tool;
|
use crate::tools::code_mode::execute_spec::create_code_mode_tool;
|
||||||
use crate::tools::context::ToolInvocation;
|
use crate::tools::context::ToolInvocation;
|
||||||
use crate::tools::effective_tool_mode;
|
use crate::tools::effective_tool_mode;
|
||||||
@@ -472,6 +473,9 @@ fn build_code_mode_executors(
|
|||||||
.sort_by(|left, right| compare_code_mode_tools(left, right, &namespace_descriptions));
|
.sort_by(|left, right| compare_code_mode_tools(left, right, &namespace_descriptions));
|
||||||
let deferred_tools =
|
let deferred_tools =
|
||||||
collect_code_mode_exec_prompt_tool_definitions(deferred_exec_prompt_tool_specs.iter());
|
collect_code_mode_exec_prompt_tool_definitions(deferred_exec_prompt_tool_specs.iter());
|
||||||
|
let default_exec_yield_time_ms =
|
||||||
|
default_exec_yield_time_override_ms(&turn_context.config.features)
|
||||||
|
.unwrap_or(codex_code_mode::DEFAULT_EXEC_YIELD_TIME_MS);
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
Arc::new(CodeModeExecuteHandler::new(
|
Arc::new(CodeModeExecuteHandler::new(
|
||||||
@@ -479,6 +483,7 @@ fn build_code_mode_executors(
|
|||||||
&enabled_tools,
|
&enabled_tools,
|
||||||
&deferred_tools,
|
&deferred_tools,
|
||||||
&namespace_descriptions,
|
&namespace_descriptions,
|
||||||
|
default_exec_yield_time_ms,
|
||||||
tool_mode == ToolMode::CodeModeOnly,
|
tool_mode == ToolMode::CodeModeOnly,
|
||||||
),
|
),
|
||||||
code_mode_nested_tool_specs,
|
code_mode_nested_tool_specs,
|
||||||
|
|||||||
@@ -1071,6 +1071,20 @@ async fn code_mode_only_exposes_code_executor_and_hides_nested_tools() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn code_mode_buffered_exec_updates_exec_description() {
|
||||||
|
let plan = probe(|turn| {
|
||||||
|
set_features(turn, &[Feature::CodeMode, Feature::CodeModeBufferedExec]);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let ToolSpec::Freeform(exec) = plan.visible_spec(codex_code_mode::PUBLIC_TOOL_NAME) else {
|
||||||
|
panic!("expected code mode exec tool");
|
||||||
|
};
|
||||||
|
assert!(exec.description.contains("Defaults to 30000 ms."));
|
||||||
|
assert!(!exec.description.contains("Defaults to 10000 ms."));
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn code_mode_only_exposes_configured_dynamic_namespace_directly() {
|
async fn code_mode_only_exposes_configured_dynamic_namespace_directly() {
|
||||||
let plan = probe_with(
|
let plan = probe_with(
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ pub enum Feature {
|
|||||||
// Experimental
|
// Experimental
|
||||||
/// Enable JavaScript code mode backed by the in-process V8 runtime.
|
/// Enable JavaScript code mode backed by the in-process V8 runtime.
|
||||||
CodeMode,
|
CodeMode,
|
||||||
|
/// Use a 30-second default yield timeout for code mode exec calls.
|
||||||
|
CodeModeBufferedExec,
|
||||||
/// Run JavaScript code mode in the standalone host process.
|
/// Run JavaScript code mode in the standalone host process.
|
||||||
CodeModeHost,
|
CodeModeHost,
|
||||||
/// Restrict model-visible tools to code mode entrypoints (`exec`, `wait`).
|
/// Restrict model-visible tools to code mode entrypoints (`exec`, `wait`).
|
||||||
@@ -859,6 +861,12 @@ pub const FEATURES: &[FeatureSpec] = &[
|
|||||||
stage: Stage::UnderDevelopment,
|
stage: Stage::UnderDevelopment,
|
||||||
default_enabled: false,
|
default_enabled: false,
|
||||||
},
|
},
|
||||||
|
FeatureSpec {
|
||||||
|
id: Feature::CodeModeBufferedExec,
|
||||||
|
key: "code_mode_buffered_exec",
|
||||||
|
stage: Stage::UnderDevelopment,
|
||||||
|
default_enabled: false,
|
||||||
|
},
|
||||||
FeatureSpec {
|
FeatureSpec {
|
||||||
id: Feature::CodeModeHost,
|
id: Feature::CodeModeHost,
|
||||||
key: "code_mode_host",
|
key: "code_mode_host",
|
||||||
|
|||||||
Reference in new issue
Block a user