mirror of
https://github.com/openai/codex.git
synced 2026-10-29 19:08:43 +00:00
logic cleanup
This commit is contained in:
@@ -48,9 +48,6 @@ pub(crate) struct CodexTurnEvent {
|
||||
pub(crate) num_input_images: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct CodexTurnSteerEvent;
|
||||
|
||||
pub(crate) fn build_track_events_context(
|
||||
model_slug: String,
|
||||
thread_id: String,
|
||||
@@ -115,9 +112,6 @@ impl AnalyticsEventsQueue {
|
||||
TrackEventsJob::TurnEvent(job) => {
|
||||
send_track_turn_event(&auth_manager, job).await;
|
||||
}
|
||||
TrackEventsJob::TurnSteer(job) => {
|
||||
send_track_turn_steer(&auth_manager, job).await;
|
||||
}
|
||||
TrackEventsJob::PluginUsed(job) => {
|
||||
send_track_plugin_used(&auth_manager, job).await;
|
||||
}
|
||||
@@ -244,19 +238,6 @@ impl AnalyticsEventsClient {
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn track_turn_steer(
|
||||
&self,
|
||||
tracking: TrackEventsContext,
|
||||
turn_steer: CodexTurnSteerEvent,
|
||||
) {
|
||||
track_turn_steer(
|
||||
&self.queue,
|
||||
Arc::clone(&self.config),
|
||||
Some(tracking),
|
||||
turn_steer,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn track_plugin_installed(&self, plugin: PluginTelemetryMetadata) {
|
||||
track_plugin_management(
|
||||
&self.queue,
|
||||
@@ -299,7 +280,6 @@ enum TrackEventsJob {
|
||||
AppMentioned(TrackAppMentionedJob),
|
||||
AppUsed(TrackAppUsedJob),
|
||||
TurnEvent(TrackTurnEventJob),
|
||||
TurnSteer(TrackTurnSteerJob),
|
||||
PluginUsed(TrackPluginUsedJob),
|
||||
PluginInstalled(TrackPluginManagementJob),
|
||||
PluginUninstalled(TrackPluginManagementJob),
|
||||
@@ -331,12 +311,6 @@ struct TrackTurnEventJob {
|
||||
turn_event: CodexTurnEvent,
|
||||
}
|
||||
|
||||
struct TrackTurnSteerJob {
|
||||
config: Arc<Config>,
|
||||
tracking: TrackEventsContext,
|
||||
turn_steer: CodexTurnSteerEvent,
|
||||
}
|
||||
|
||||
struct TrackPluginUsedJob {
|
||||
config: Arc<Config>,
|
||||
tracking: TrackEventsContext,
|
||||
@@ -420,39 +394,24 @@ struct CodexAppUsedEventRequest {
|
||||
event_params: CodexAppMetadata,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize)]
|
||||
#[derive(Serialize)]
|
||||
struct CodexTurnEventParams {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
thread_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
turn_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
thread_id: String,
|
||||
turn_id: String,
|
||||
product_client_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
model_slug: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
model: Option<String>,
|
||||
submission_type: Option<SubmissionType>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
model_provider: Option<String>,
|
||||
model_provider: String,
|
||||
sandbox_policy: Option<&'static str>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
reasoning_effort: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
reasoning_summary: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
service_tier: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
approval_policy: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
approvals_reviewer: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
sandbox_network_access: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
service_tier: String,
|
||||
approval_policy: String,
|
||||
approvals_reviewer: String,
|
||||
sandbox_network_access: bool,
|
||||
collaboration_mode: Option<&'static str>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
personality: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
num_input_images: Option<usize>,
|
||||
num_input_images: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -582,26 +541,6 @@ pub(crate) fn track_turn_event(
|
||||
queue.try_send(job);
|
||||
}
|
||||
|
||||
pub(crate) fn track_turn_steer(
|
||||
queue: &AnalyticsEventsQueue,
|
||||
config: Arc<Config>,
|
||||
tracking: Option<TrackEventsContext>,
|
||||
turn_steer: CodexTurnSteerEvent,
|
||||
) {
|
||||
if config.analytics_enabled == Some(false) {
|
||||
return;
|
||||
}
|
||||
let Some(tracking) = tracking else {
|
||||
return;
|
||||
};
|
||||
let job = TrackEventsJob::TurnSteer(TrackTurnSteerJob {
|
||||
config,
|
||||
tracking,
|
||||
turn_steer,
|
||||
});
|
||||
queue.try_send(job);
|
||||
}
|
||||
|
||||
pub(crate) fn track_plugin_used(
|
||||
queue: &AnalyticsEventsQueue,
|
||||
config: Arc<Config>,
|
||||
@@ -741,20 +680,6 @@ async fn send_track_turn_event(auth_manager: &AuthManager, job: TrackTurnEventJo
|
||||
send_track_events(auth_manager, config, events).await;
|
||||
}
|
||||
|
||||
async fn send_track_turn_steer(auth_manager: &AuthManager, job: TrackTurnSteerJob) {
|
||||
let TrackTurnSteerJob {
|
||||
config,
|
||||
tracking,
|
||||
turn_steer,
|
||||
} = job;
|
||||
let events = vec![TrackEventRequest::TurnEvent(CodexTurnEventRequest {
|
||||
event_type: "codex_turn_event",
|
||||
event_params: codex_turn_steer_event_params(&tracking, turn_steer),
|
||||
})];
|
||||
|
||||
send_track_events(auth_manager, config, events).await;
|
||||
}
|
||||
|
||||
async fn send_track_plugin_used(auth_manager: &AuthManager, job: TrackPluginUsedJob) {
|
||||
let TrackPluginUsedJob {
|
||||
config,
|
||||
@@ -824,40 +749,25 @@ fn codex_turn_event_params(
|
||||
turn_event: CodexTurnEvent,
|
||||
) -> CodexTurnEventParams {
|
||||
CodexTurnEventParams {
|
||||
thread_id: Some(tracking.thread_id.clone()),
|
||||
turn_id: Some(tracking.turn_id.clone()),
|
||||
thread_id: tracking.thread_id.clone(),
|
||||
turn_id: tracking.turn_id.clone(),
|
||||
product_client_id: Some(crate::default_client::originator().value),
|
||||
model_slug: Some(tracking.model_slug.clone()),
|
||||
model: Some(tracking.model_slug.clone()),
|
||||
submission_type: turn_event.submission_type,
|
||||
model_provider: Some(turn_event.model_provider),
|
||||
model_provider: turn_event.model_provider,
|
||||
sandbox_policy: Some(sandbox_policy_mode(&turn_event.sandbox_policy)),
|
||||
reasoning_effort: turn_event.reasoning_effort.map(|value| value.to_string()),
|
||||
reasoning_summary: reasoning_summary_mode(turn_event.reasoning_summary),
|
||||
service_tier: Some(
|
||||
turn_event
|
||||
.service_tier
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_else(|| "default".to_string()),
|
||||
),
|
||||
approval_policy: Some(turn_event.approval_policy.to_string()),
|
||||
approvals_reviewer: Some(turn_event.approvals_reviewer.to_string()),
|
||||
sandbox_network_access: Some(turn_event.sandbox_network_access),
|
||||
service_tier: turn_event
|
||||
.service_tier
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_else(|| "default".to_string()),
|
||||
approval_policy: turn_event.approval_policy.to_string(),
|
||||
approvals_reviewer: turn_event.approvals_reviewer.to_string(),
|
||||
sandbox_network_access: turn_event.sandbox_network_access,
|
||||
collaboration_mode: Some(collaboration_mode_mode(turn_event.collaboration_mode)),
|
||||
personality: personality_mode(turn_event.personality),
|
||||
num_input_images: Some(turn_event.num_input_images),
|
||||
}
|
||||
}
|
||||
|
||||
fn codex_turn_steer_event_params(
|
||||
tracking: &TrackEventsContext,
|
||||
_turn_steer: CodexTurnSteerEvent,
|
||||
) -> CodexTurnEventParams {
|
||||
CodexTurnEventParams {
|
||||
thread_id: Some(tracking.thread_id.clone()),
|
||||
turn_id: Some(tracking.turn_id.clone()),
|
||||
product_client_id: Some(crate::default_client::originator().value),
|
||||
model_slug: Some(tracking.model_slug.clone()),
|
||||
..Default::default()
|
||||
num_input_images: turn_event.num_input_images,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ use super::CodexPluginEventRequest;
|
||||
use super::CodexPluginUsedEventRequest;
|
||||
use super::CodexTurnEvent;
|
||||
use super::CodexTurnEventRequest;
|
||||
use super::CodexTurnSteerEvent;
|
||||
use super::InvocationType;
|
||||
use super::SubmissionType;
|
||||
use super::TrackEventRequest;
|
||||
@@ -15,7 +14,6 @@ use super::codex_app_metadata;
|
||||
use super::codex_plugin_metadata;
|
||||
use super::codex_plugin_used_metadata;
|
||||
use super::codex_turn_event_params;
|
||||
use super::codex_turn_steer_event_params;
|
||||
use super::normalize_path_for_skill_id;
|
||||
use crate::plugins::AppConnectorId;
|
||||
use crate::plugins::PluginCapabilitySummary;
|
||||
@@ -227,7 +225,7 @@ fn turn_event_serializes_expected_shape() {
|
||||
),
|
||||
});
|
||||
|
||||
let payload = serde_json::to_value(&event).expect("serialize codex turn event");
|
||||
let payload = serde_json::to_value(&event).expect("serialize turn event");
|
||||
|
||||
assert_eq!(
|
||||
payload,
|
||||
@@ -237,7 +235,7 @@ fn turn_event_serializes_expected_shape() {
|
||||
"thread_id": "thread-2",
|
||||
"turn_id": "turn-2",
|
||||
"product_client_id": crate::default_client::originator().value,
|
||||
"model_slug": "gpt-5",
|
||||
"model": "gpt-5",
|
||||
"submission_type": "prompt",
|
||||
"model_provider": "openai",
|
||||
"sandbox_policy": "read_only",
|
||||
@@ -255,33 +253,6 @@ fn turn_event_serializes_expected_shape() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_steer_event_serializes_expected_shape() {
|
||||
let tracking = TrackEventsContext {
|
||||
model_slug: "gpt-5".to_string(),
|
||||
thread_id: "thread-2".to_string(),
|
||||
turn_id: "turn-2".to_string(),
|
||||
};
|
||||
let event = TrackEventRequest::TurnEvent(CodexTurnEventRequest {
|
||||
event_type: "codex_turn_event",
|
||||
event_params: codex_turn_steer_event_params(&tracking, CodexTurnSteerEvent),
|
||||
});
|
||||
|
||||
let payload = serde_json::to_value(&event).expect("serialize codex turn steer event");
|
||||
|
||||
assert_eq!(
|
||||
payload,
|
||||
json!({
|
||||
"event_type": "codex_turn_event",
|
||||
"event_params": {
|
||||
"thread_id": "thread-2",
|
||||
"turn_id": "turn-2",
|
||||
"product_client_id": crate::default_client::originator().value,
|
||||
"model_slug": "gpt-5"
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugin_used_event_serializes_expected_shape() {
|
||||
|
||||
+42
-47
@@ -15,7 +15,6 @@ use crate::agent::agent_status_from_event;
|
||||
use crate::analytics_client::AnalyticsEventsClient;
|
||||
use crate::analytics_client::AppInvocation;
|
||||
use crate::analytics_client::CodexTurnEvent;
|
||||
use crate::analytics_client::CodexTurnSteerEvent;
|
||||
use crate::analytics_client::InvocationType;
|
||||
use crate::analytics_client::build_track_events_context;
|
||||
use crate::apps::render_apps_section;
|
||||
@@ -238,9 +237,6 @@ pub(crate) struct PreviousTurnSettings {
|
||||
|
||||
use crate::exec_policy::ExecPolicyUpdateError;
|
||||
use crate::feedback_tags;
|
||||
use crate::file_watcher::FileWatcher;
|
||||
use crate::file_watcher::FileWatcherEvent;
|
||||
use crate::git_info::get_git_repo_root;
|
||||
use crate::guardian::GuardianReviewSessionManager;
|
||||
use crate::hook_runtime::PendingInputHookDisposition;
|
||||
use crate::hook_runtime::inspect_pending_input;
|
||||
@@ -325,6 +321,8 @@ use crate::skills::injection::ToolMentionKind;
|
||||
use crate::skills::injection::app_id_from_path;
|
||||
use crate::skills::injection::tool_kind_for_path;
|
||||
use crate::skills::resolve_skill_dependencies_for_turn;
|
||||
use crate::skills_watcher::SkillsWatcher;
|
||||
use crate::skills_watcher::SkillsWatcherEvent;
|
||||
use crate::state::ActiveTurn;
|
||||
use crate::state::SessionServices;
|
||||
use crate::state::SessionState;
|
||||
@@ -353,6 +351,7 @@ use crate::unified_exec::UnifiedExecProcessManager;
|
||||
use crate::util::backoff;
|
||||
use crate::windows_sandbox::WindowsSandboxLevelExt;
|
||||
use codex_async_utils::OrCancelExt;
|
||||
use codex_git_utils::get_git_repo_root;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_otel::TelemetryAuthMode;
|
||||
use codex_otel::metrics::names::THREAD_STARTED_METRIC;
|
||||
@@ -406,7 +405,7 @@ pub(crate) struct CodexSpawnArgs {
|
||||
pub(crate) skills_manager: Arc<SkillsManager>,
|
||||
pub(crate) plugins_manager: Arc<PluginsManager>,
|
||||
pub(crate) mcp_manager: Arc<McpManager>,
|
||||
pub(crate) file_watcher: Arc<FileWatcher>,
|
||||
pub(crate) skills_watcher: Arc<SkillsWatcher>,
|
||||
pub(crate) conversation_history: InitialHistory,
|
||||
pub(crate) session_source: SessionSource,
|
||||
pub(crate) agent_control: AgentControl,
|
||||
@@ -459,7 +458,7 @@ impl Codex {
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
file_watcher,
|
||||
skills_watcher,
|
||||
conversation_history,
|
||||
session_source,
|
||||
agent_control,
|
||||
@@ -647,7 +646,7 @@ impl Codex {
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
mcp_manager.clone(),
|
||||
file_watcher,
|
||||
skills_watcher,
|
||||
agent_control,
|
||||
)
|
||||
.await
|
||||
@@ -1303,13 +1302,13 @@ impl Session {
|
||||
self.out_of_band_elicitation_paused.send_replace(paused);
|
||||
}
|
||||
|
||||
fn start_file_watcher_listener(self: &Arc<Self>) {
|
||||
let mut rx = self.services.file_watcher.subscribe();
|
||||
fn start_skills_watcher_listener(self: &Arc<Self>) {
|
||||
let mut rx = self.services.skills_watcher.subscribe();
|
||||
let weak_sess = Arc::downgrade(self);
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(FileWatcherEvent::SkillsChanged { .. }) => {
|
||||
Ok(SkillsWatcherEvent::SkillsChanged { .. }) => {
|
||||
let Some(sess) = weak_sess.upgrade() else {
|
||||
break;
|
||||
};
|
||||
@@ -1446,7 +1445,7 @@ impl Session {
|
||||
skills_manager: Arc<SkillsManager>,
|
||||
plugins_manager: Arc<PluginsManager>,
|
||||
mcp_manager: Arc<McpManager>,
|
||||
file_watcher: Arc<FileWatcher>,
|
||||
skills_watcher: Arc<SkillsWatcher>,
|
||||
agent_control: AgentControl,
|
||||
) -> anyhow::Result<Arc<Self>> {
|
||||
debug!(
|
||||
@@ -1865,7 +1864,7 @@ impl Session {
|
||||
skills_manager,
|
||||
plugins_manager: Arc::clone(&plugins_manager),
|
||||
mcp_manager: Arc::clone(&mcp_manager),
|
||||
file_watcher,
|
||||
skills_watcher,
|
||||
agent_control,
|
||||
network_proxy,
|
||||
network_approval: Arc::clone(&network_approval),
|
||||
@@ -1944,7 +1943,7 @@ impl Session {
|
||||
}
|
||||
|
||||
// Start the watcher after SessionConfigured so it cannot emit earlier events.
|
||||
sess.start_file_watcher_listener();
|
||||
sess.start_skills_watcher_listener();
|
||||
// Construct sandbox_state before MCP startup so it can be sent to each
|
||||
// MCP server immediately after it becomes ready (avoiding blocking).
|
||||
let sandbox_state = SandboxState {
|
||||
@@ -2749,16 +2748,6 @@ impl Session {
|
||||
))
|
||||
}
|
||||
|
||||
async fn active_turn_tracking(&self) -> Option<crate::analytics_client::TrackEventsContext> {
|
||||
let active = self.active_turn.lock().await;
|
||||
let (_, task) = active.as_ref()?.tasks.first()?;
|
||||
Some(build_track_events_context(
|
||||
task.turn_context.model_info.slug.clone(),
|
||||
self.conversation_id.to_string(),
|
||||
task.turn_context.sub_id.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn record_execpolicy_amendment_message(
|
||||
&self,
|
||||
sub_id: &str,
|
||||
@@ -3906,7 +3895,6 @@ impl Session {
|
||||
let Some((active_turn_id, _)) = active_turn.tasks.first() else {
|
||||
return Err(SteerInputError::NoActiveTurn(input));
|
||||
};
|
||||
let active_turn_id = active_turn_id.clone();
|
||||
|
||||
if let Some(expected_turn_id) = expected_turn_id
|
||||
&& expected_turn_id != active_turn_id
|
||||
@@ -3934,14 +3922,7 @@ impl Session {
|
||||
|
||||
let mut turn_state = active_turn.turn_state.lock().await;
|
||||
turn_state.push_pending_input(input.into());
|
||||
drop(turn_state);
|
||||
drop(active);
|
||||
if let Some(tracking) = self.active_turn_tracking().await {
|
||||
self.services
|
||||
.analytics_events_client
|
||||
.track_turn_steer(tracking, CodexTurnSteerEvent);
|
||||
}
|
||||
Ok(active_turn_id)
|
||||
Ok(active_turn_id.clone())
|
||||
}
|
||||
|
||||
/// Returns the input if there was no task running to inject into.
|
||||
@@ -3985,6 +3966,17 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn pending_input_snapshot(&self) -> Vec<ResponseInputItem> {
|
||||
let active = self.active_turn.lock().await;
|
||||
match active.as_ref() {
|
||||
Some(at) => {
|
||||
let ts = at.turn_state.lock().await;
|
||||
ts.pending_input_snapshot()
|
||||
}
|
||||
None => Vec::with_capacity(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Queue response items to be injected into the next active turn created for this session.
|
||||
pub(crate) async fn queue_response_items_for_next_turn(&self, items: Vec<ResponseInputItem>) {
|
||||
if items.is_empty() {
|
||||
@@ -3999,6 +3991,12 @@ impl Session {
|
||||
std::mem::take(&mut *self.idle_pending_input.lock().await)
|
||||
}
|
||||
|
||||
pub(crate) async fn queued_response_items_for_next_turn_snapshot(
|
||||
&self,
|
||||
) -> Vec<ResponseInputItem> {
|
||||
self.idle_pending_input.lock().await.clone()
|
||||
}
|
||||
|
||||
pub(crate) async fn has_queued_response_items_for_next_turn(&self) -> bool {
|
||||
!self.idle_pending_input.lock().await.is_empty()
|
||||
}
|
||||
@@ -4677,27 +4675,27 @@ mod handlers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Records an inter-agent assistant envelope and, when requested, wakes the recipient by
|
||||
/// starting a regular turn if the session is currently idle.
|
||||
pub async fn inter_agent_communication(
|
||||
sess: &Arc<Session>,
|
||||
sub_id: String,
|
||||
communication: InterAgentCommunication,
|
||||
) {
|
||||
let pending_item = communication.to_response_input_item();
|
||||
if sess
|
||||
.inject_response_items(vec![pending_item.clone()])
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
if let Ok(()) = sess.inject_response_items(vec![pending_item.clone()]).await {
|
||||
return;
|
||||
}
|
||||
|
||||
let turn_context = sess.new_default_turn_with_sub_id(sub_id).await;
|
||||
sess.maybe_emit_unknown_model_warning_for_turn(turn_context.as_ref())
|
||||
.await;
|
||||
sess.queue_response_items_for_next_turn(vec![pending_item])
|
||||
.await;
|
||||
sess.spawn_task(turn_context, Vec::new(), crate::tasks::RegularTask::new())
|
||||
.await;
|
||||
if communication.trigger_turn {
|
||||
let turn_context = sess.new_default_turn_with_sub_id(sub_id).await;
|
||||
sess.maybe_emit_unknown_model_warning_for_turn(turn_context.as_ref())
|
||||
.await;
|
||||
sess.spawn_task(turn_context, Vec::new(), crate::tasks::RegularTask::new())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_user_shell_command(sess: &Arc<Session>, sub_id: String, command: String) {
|
||||
@@ -6025,14 +6023,11 @@ pub(crate) async fn run_turn(
|
||||
sess.services.analytics_events_client.track_turn_event(
|
||||
tracking,
|
||||
CodexTurnEvent {
|
||||
submission_type: match submission_type {
|
||||
SubmissionType::Prompt => Some(SubmissionType::Prompt),
|
||||
SubmissionType::PromptQueued => Some(SubmissionType::PromptQueued),
|
||||
},
|
||||
submission_type: Some(submission_type),
|
||||
model_provider: turn_context.config.model_provider_id.clone(),
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
reasoning_effort: turn_context.reasoning_effort,
|
||||
reasoning_summary: turn_context.reasoning_summary,
|
||||
reasoning_summary: Some(turn_context.reasoning_summary),
|
||||
service_tier: turn_context.config.service_tier,
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
approvals_reviewer: turn_context.config.approvals_reviewer,
|
||||
|
||||
Reference in New Issue
Block a user