mirror of
https://github.com/openai/codex.git
synced 2026-10-19 16:17:49 +00:00
Expose disabled plugin settings in the app-server API (#44905)
## What changed - Accept `disabledPluginIds` in `thread/settings/update` and `turn/start`. A supplied list replaces the saved selection; omission or `null` preserves it, and `[]` clears it. - Return the selection in thread start, resume, and fork responses and `thread/settings/updated` notifications. Update generated schemas and client types. - Restore disabled plugin IDs from the history retained at the requested fork boundary, preserving explicit overrides. The selection persists across resume but does not yet filter plugin capabilities. ## Testing Add coverage for replacing, preserving, and clearing selections without inference; turn-start notifications; resume; and fork boundaries across legacy and paginated history, with loaded and restarted parents. Add a core regression test for history restoration and explicit clearing. GitOrigin-RevId: 654a8c2a0527228d422c0dd4919228447e2663db
This commit is contained in:
@@ -287,6 +287,7 @@ fn sample_thread_start_response(
|
||||
model: &str,
|
||||
) -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadStart(ThreadStartResponse {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
thread: sample_thread_with_metadata(
|
||||
thread_id,
|
||||
ephemeral,
|
||||
@@ -352,6 +353,7 @@ fn sample_thread_resume_response_with_source(
|
||||
parent_thread_id: Option<String>,
|
||||
) -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadResume(ThreadResumeResponse {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
thread: sample_thread_with_metadata(
|
||||
thread_id,
|
||||
ephemeral,
|
||||
|
||||
@@ -625,6 +625,7 @@ fn sample_thread(thread_id: &str) -> Thread {
|
||||
|
||||
fn sample_thread_start_response() -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadStart(ThreadStartResponse {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
thread: sample_thread("thread-1"),
|
||||
model: "gpt-5".to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
@@ -643,6 +644,7 @@ fn sample_thread_start_response() -> ClientResponsePayload {
|
||||
|
||||
fn sample_thread_resume_response() -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadResume(ThreadResumeResponse {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
thread: sample_thread("thread-2"),
|
||||
model: "gpt-5".to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
@@ -664,6 +666,7 @@ fn sample_thread_resume_response() -> ClientResponsePayload {
|
||||
|
||||
fn sample_thread_fork_response() -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadFork(ThreadForkResponse {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
thread: sample_thread("thread-3"),
|
||||
model: "gpt-5".to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
|
||||
@@ -5470,6 +5470,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"description": "Replace this thread's disabled plugin IDs. Omitted/null preserves the list; [] clears it.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"effort": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -5895,6 +5895,14 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"default": [],
|
||||
"description": "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"effort": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
+42
@@ -21105,6 +21105,14 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"default": [],
|
||||
"description": "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"instructionSources": {
|
||||
"default": [],
|
||||
"description": "Environment-native paths to instruction source files currently loaded for this thread.",
|
||||
@@ -23353,6 +23361,14 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"default": [],
|
||||
"description": "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"instructionSources": {
|
||||
"default": [],
|
||||
"description": "Environment-native paths to instruction source files currently loaded for this thread.",
|
||||
@@ -23832,6 +23848,14 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"default": [],
|
||||
"description": "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"effort": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -24105,6 +24129,14 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"default": [],
|
||||
"description": "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"instructionSources": {
|
||||
"default": [],
|
||||
"description": "Environment-native paths to instruction source files currently loaded for this thread.",
|
||||
@@ -25135,6 +25167,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"description": "Replace this thread's disabled plugin IDs. Omitted/null preserves the list; [] clears it.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"effort": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
+42
@@ -18819,6 +18819,14 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"default": [],
|
||||
"description": "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"instructionSources": {
|
||||
"default": [],
|
||||
"description": "Environment-native paths to instruction source files currently loaded for this thread.",
|
||||
@@ -21067,6 +21075,14 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"default": [],
|
||||
"description": "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"instructionSources": {
|
||||
"default": [],
|
||||
"description": "Environment-native paths to instruction source files currently loaded for this thread.",
|
||||
@@ -21546,6 +21562,14 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"default": [],
|
||||
"description": "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"effort": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -21819,6 +21843,14 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"default": [],
|
||||
"description": "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"instructionSources": {
|
||||
"default": [],
|
||||
"description": "Environment-native paths to instruction source files currently loaded for this thread.",
|
||||
@@ -22849,6 +22881,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"description": "Replace this thread's disabled plugin IDs. Omitted/null preserves the list; [] clears it.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"effort": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -2942,6 +2942,14 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"default": [],
|
||||
"description": "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"instructionSources": {
|
||||
"default": [],
|
||||
"description": "Environment-native paths to instruction source files currently loaded for this thread.",
|
||||
|
||||
@@ -2968,6 +2968,14 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"default": [],
|
||||
"description": "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"instructionSources": {
|
||||
"default": [],
|
||||
"description": "Environment-native paths to instruction source files currently loaded for this thread.",
|
||||
|
||||
+8
@@ -323,6 +323,14 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"default": [],
|
||||
"description": "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"effort": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -2942,6 +2942,14 @@
|
||||
"cwd": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"default": [],
|
||||
"description": "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"instructionSources": {
|
||||
"default": [],
|
||||
"description": "Environment-native paths to instruction source files currently loaded for this thread.",
|
||||
|
||||
@@ -737,6 +737,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"disabledPluginIds": {
|
||||
"description": "Replace this thread's disabled plugin IDs. Omitted/null preserves the list; [] clears it.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"effort": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -9,7 +9,10 @@ import type { AskForApproval } from "./AskForApproval";
|
||||
import type { SandboxPolicy } from "./SandboxPolicy";
|
||||
import type { Thread } from "./Thread";
|
||||
|
||||
export type ThreadForkResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: string | null, cwd: AbsolutePathBuf, /**
|
||||
export type ThreadForkResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: string | null, /**
|
||||
* Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.
|
||||
*/
|
||||
disabledPluginIds: Array<string>, cwd: AbsolutePathBuf, /**
|
||||
* Environment-native paths to instruction source files currently loaded for this thread.
|
||||
*/
|
||||
instructionSources: Array<LegacyAppPathString>, approvalPolicy: AskForApproval, /**
|
||||
|
||||
@@ -9,7 +9,10 @@ import type { AskForApproval } from "./AskForApproval";
|
||||
import type { SandboxPolicy } from "./SandboxPolicy";
|
||||
import type { Thread } from "./Thread";
|
||||
|
||||
export type ThreadResumeResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: string | null, cwd: AbsolutePathBuf, /**
|
||||
export type ThreadResumeResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: string | null, /**
|
||||
* Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.
|
||||
*/
|
||||
disabledPluginIds: Array<string>, cwd: AbsolutePathBuf, /**
|
||||
* Environment-native paths to instruction source files currently loaded for this thread.
|
||||
*/
|
||||
instructionSources: Array<LegacyAppPathString>, approvalPolicy: AskForApproval, /**
|
||||
|
||||
@@ -11,4 +11,7 @@ import type { ApprovalsReviewer } from "./ApprovalsReviewer";
|
||||
import type { AskForApproval } from "./AskForApproval";
|
||||
import type { SandboxPolicy } from "./SandboxPolicy";
|
||||
|
||||
export type ThreadSettings = {cwd: AbsolutePathBuf, approvalPolicy: AskForApproval, approvalsReviewer: ApprovalsReviewer, sandboxPolicy: SandboxPolicy, activePermissionProfile: ActivePermissionProfile | null, model: string, modelProvider: string, serviceTier: string | null, effort: ReasoningEffort | null, summary: ReasoningSummary | null, collaborationMode: CollaborationMode, personality: Personality | null};
|
||||
export type ThreadSettings = {/**
|
||||
* Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.
|
||||
*/
|
||||
disabledPluginIds: Array<string>, cwd: AbsolutePathBuf, approvalPolicy: AskForApproval, approvalsReviewer: ApprovalsReviewer, sandboxPolicy: SandboxPolicy, activePermissionProfile: ActivePermissionProfile | null, model: string, modelProvider: string, serviceTier: string | null, effort: ReasoningEffort | null, summary: ReasoningSummary | null, collaborationMode: CollaborationMode, personality: Personality | null};
|
||||
|
||||
@@ -9,7 +9,10 @@ import type { AskForApproval } from "./AskForApproval";
|
||||
import type { SandboxPolicy } from "./SandboxPolicy";
|
||||
import type { Thread } from "./Thread";
|
||||
|
||||
export type ThreadStartResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: string | null, cwd: AbsolutePathBuf, /**
|
||||
export type ThreadStartResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: string | null, /**
|
||||
* Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.
|
||||
*/
|
||||
disabledPluginIds: Array<string>, cwd: AbsolutePathBuf, /**
|
||||
* Environment-native paths to instruction source files currently loaded for this thread.
|
||||
*/
|
||||
instructionSources: Array<LegacyAppPathString>, approvalPolicy: AskForApproval, /**
|
||||
|
||||
@@ -11,7 +11,11 @@ import type { SandboxPolicy } from "./SandboxPolicy";
|
||||
import type { TurnToolOutput } from "./TurnToolOutput";
|
||||
import type { UserInput } from "./UserInput";
|
||||
|
||||
export type TurnStartParams = {threadId: string, clientUserMessageId?: string | null, input: Array<UserInput>, /**
|
||||
export type TurnStartParams = {threadId: string, /**
|
||||
* Replace this thread's disabled plugin IDs.
|
||||
* Omitted/null preserves the list; [] clears it.
|
||||
*/
|
||||
disabledPluginIds?: Array<string> | null, clientUserMessageId?: string | null, input: Array<UserInput>, /**
|
||||
* Optional source classification for the caller that starts this turn.
|
||||
* Ignored when this request steers an already-active turn.
|
||||
*/
|
||||
|
||||
@@ -3184,6 +3184,7 @@ mod tests {
|
||||
let response = ClientResponse::ThreadStart {
|
||||
request_id: RequestId::Integer(7),
|
||||
response: v2::ThreadStartResponse {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
thread: v2::Thread {
|
||||
originator: None,
|
||||
environments: None,
|
||||
@@ -3283,6 +3284,7 @@ mod tests {
|
||||
"model": "gpt-5",
|
||||
"modelProvider": "openai",
|
||||
"serviceTier": null,
|
||||
"disabledPluginIds": [],
|
||||
"cwd": absolute_path_string("tmp"),
|
||||
"runtimeWorkspaceRoots": [],
|
||||
"instructionSources": [absolute_path_string("tmp/AGENTS.md")],
|
||||
@@ -4477,6 +4479,7 @@ mod tests {
|
||||
ServerNotification::ThreadSettingsUpdated(v2::ThreadSettingsUpdatedNotification {
|
||||
thread_id: "thr_123".to_string(),
|
||||
thread_settings: v2::ThreadSettings {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
cwd: absolute_path("/tmp/repo"),
|
||||
approval_policy: v2::AskForApproval::Never,
|
||||
approvals_reviewer: v2::ApprovalsReviewer::User,
|
||||
|
||||
@@ -271,6 +271,7 @@ fn thread_resume_params_accept_turns_page_bootstrap() {
|
||||
#[test]
|
||||
fn thread_resume_response_round_trips_initial_turns_page() {
|
||||
let response = ThreadResumeResponse {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
thread: Thread {
|
||||
originator: Some("future_client".to_string()),
|
||||
environments: Some(vec![ThreadEnvironment {
|
||||
@@ -4815,6 +4816,7 @@ fn turn_start_params_preserve_explicit_null_service_tier() {
|
||||
);
|
||||
|
||||
let without_override = TurnStartParams {
|
||||
disabled_plugin_ids: None,
|
||||
thread_id: "thread_123".to_string(),
|
||||
client_user_message_id: None,
|
||||
input: vec![],
|
||||
|
||||
@@ -183,6 +183,9 @@ pub struct ThreadStartResponse {
|
||||
pub model: String,
|
||||
pub model_provider: String,
|
||||
pub service_tier: Option<String>,
|
||||
/// Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.
|
||||
#[serde(default)]
|
||||
pub disabled_plugin_ids: Vec<String>,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
/// Thread-scoped runtime workspace roots used to materialize
|
||||
/// `:workspace_roots`.
|
||||
@@ -225,6 +228,10 @@ impl ThreadStartResponse {
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadSettingsUpdateParams {
|
||||
pub thread_id: String,
|
||||
/// Replace this thread's disabled plugin IDs.
|
||||
/// Omitted/null preserves the list; [] clears it.
|
||||
#[ts(optional = nullable)]
|
||||
pub disabled_plugin_ids: Option<Vec<String>>,
|
||||
/// Override the working directory for subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub cwd: Option<PathBuf>,
|
||||
@@ -287,6 +294,9 @@ pub struct ThreadSettingsUpdateResponse {}
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadSettings {
|
||||
/// Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.
|
||||
#[serde(default)]
|
||||
pub disabled_plugin_ids: Vec<String>,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
pub approval_policy: AskForApproval,
|
||||
pub approvals_reviewer: ApprovalsReviewer,
|
||||
@@ -417,6 +427,9 @@ pub struct ThreadResumeResponse {
|
||||
pub model: String,
|
||||
pub model_provider: String,
|
||||
pub service_tier: Option<String>,
|
||||
/// Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.
|
||||
#[serde(default)]
|
||||
pub disabled_plugin_ids: Vec<String>,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
/// Thread-scoped runtime workspace roots used to materialize
|
||||
/// `:workspace_roots`.
|
||||
@@ -608,6 +621,9 @@ pub struct ThreadForkResponse {
|
||||
pub model: String,
|
||||
pub model_provider: String,
|
||||
pub service_tier: Option<String>,
|
||||
/// Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.
|
||||
#[serde(default)]
|
||||
pub disabled_plugin_ids: Vec<String>,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
/// Thread-scoped runtime workspace roots used to materialize
|
||||
/// `:workspace_roots`.
|
||||
|
||||
@@ -165,6 +165,10 @@ pub struct TurnToolOutput {
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnStartParams {
|
||||
pub thread_id: String,
|
||||
/// Replace this thread's disabled plugin IDs.
|
||||
/// Omitted/null preserves the list; [] clears it.
|
||||
#[ts(optional = nullable)]
|
||||
pub disabled_plugin_ids: Option<Vec<String>>,
|
||||
#[ts(optional = nullable)]
|
||||
pub client_user_message_id: Option<String>,
|
||||
pub input: Vec<UserInput>,
|
||||
|
||||
@@ -183,6 +183,20 @@ Attachments record the resources currently associated with a thread, independent
|
||||
|
||||
Attachment creation and deletion requests using the same thread ID are serialized across connections. The requesting client receives its response before the compact update is broadcast, and duplicate creates or absent deletes do not emit updates. Deleting the owning thread removes its attachments under the same lifecycle exclusion; queued attachment mutations then report that the thread was not found.
|
||||
|
||||
# Thread plugin settings
|
||||
|
||||
`thread/settings/update` and `turn/start` accept `disabledPluginIds`, a list of
|
||||
`PluginSummary.id` values from `plugin/list`, in the
|
||||
`<plugin-name>@<marketplace-name>` format. A supplied list replaces the selection;
|
||||
omission or `null` preserves it, and `[]` clears it. Saving this selection does
|
||||
not yet filter plugin capabilities.
|
||||
|
||||
Read the selection from `threadSettings.disabledPluginIds` in
|
||||
`thread/settings/updated` notifications, or from `disabledPluginIds` in
|
||||
`thread/start`, `thread/resume`, and `thread/fork` responses. Selections persist
|
||||
across resume. Forks restore the selection from the history retained at the
|
||||
requested fork boundary.
|
||||
|
||||
# MCP server capabilities
|
||||
|
||||
`mcpServerStatus/list` returns `serverCapabilities` for each initialized MCP server
|
||||
|
||||
@@ -662,6 +662,7 @@ async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {
|
||||
ClientRequest::TurnStart {
|
||||
request_id: RequestId::Integer(3),
|
||||
params: TurnStartParams {
|
||||
disabled_plugin_ids: None,
|
||||
environments: None,
|
||||
thread_id,
|
||||
client_user_message_id: None,
|
||||
|
||||
@@ -730,6 +730,7 @@ pub(super) async fn handle_pending_thread_resume_request(
|
||||
let cwd = config_snapshot.cwd().clone();
|
||||
let ThreadConfigSnapshot {
|
||||
model,
|
||||
disabled_plugin_ids,
|
||||
model_provider_id,
|
||||
service_tier,
|
||||
approval_policy,
|
||||
@@ -748,6 +749,7 @@ pub(super) async fn handle_pending_thread_resume_request(
|
||||
|
||||
let response = ThreadResumeResponse {
|
||||
thread,
|
||||
disabled_plugin_ids,
|
||||
model,
|
||||
model_provider: model_provider_id,
|
||||
service_tier,
|
||||
|
||||
@@ -1617,6 +1617,7 @@ impl ThreadRequestProcessor {
|
||||
|
||||
let response = ThreadStartResponse {
|
||||
thread: thread.clone(),
|
||||
disabled_plugin_ids: config_snapshot.disabled_plugin_ids,
|
||||
model: config_snapshot.model,
|
||||
model_provider: config_snapshot.model_provider_id,
|
||||
service_tier: config_snapshot.service_tier,
|
||||
@@ -4177,6 +4178,7 @@ impl ThreadRequestProcessor {
|
||||
let thread_originator = config_snapshot.originator.clone();
|
||||
let response = ThreadResumeResponse {
|
||||
thread,
|
||||
disabled_plugin_ids: config_snapshot.disabled_plugin_ids,
|
||||
model: session_configured.model,
|
||||
model_provider: session_configured.model_provider_id,
|
||||
service_tier: session_configured.service_tier,
|
||||
@@ -5369,6 +5371,7 @@ impl ThreadRequestProcessor {
|
||||
let thread_originator = config_snapshot.originator.clone();
|
||||
let response = ThreadForkResponse {
|
||||
thread: thread.clone(),
|
||||
disabled_plugin_ids: config_snapshot.disabled_plugin_ids,
|
||||
model: session_configured.model,
|
||||
model_provider: session_configured.model_provider_id,
|
||||
service_tier: session_configured.service_tier,
|
||||
|
||||
@@ -679,6 +679,7 @@ mod thread_processor_behavior_tests {
|
||||
initial_turns_page: None,
|
||||
};
|
||||
let config_snapshot = ThreadConfigSnapshot {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
model: "gpt-5".to_string(),
|
||||
model_provider_id: "openai".to_string(),
|
||||
service_tier: Some("flex".to_string()),
|
||||
|
||||
@@ -176,6 +176,7 @@ pub(crate) fn thread_settings_from_config_snapshot(
|
||||
config_snapshot: &ThreadConfigSnapshot,
|
||||
) -> ThreadSettings {
|
||||
ThreadSettings {
|
||||
disabled_plugin_ids: config_snapshot.disabled_plugin_ids.clone(),
|
||||
cwd: config_snapshot.cwd().clone(),
|
||||
approval_policy: config_snapshot.approval_policy.into(),
|
||||
approvals_reviewer: config_snapshot.approvals_reviewer.into(),
|
||||
|
||||
@@ -122,6 +122,7 @@ struct ThreadEnvironmentOverride {
|
||||
|
||||
struct ThreadSettingsBuildParams {
|
||||
method: &'static str,
|
||||
disabled_plugin_ids: Option<Vec<String>>,
|
||||
environment_override: ThreadEnvironmentOverride,
|
||||
approval_policy: Option<codex_app_server_protocol::AskForApproval>,
|
||||
approvals_reviewer: Option<codex_app_server_protocol::ApprovalsReviewer>,
|
||||
@@ -627,6 +628,7 @@ impl TurnRequestProcessor {
|
||||
thread.as_ref(),
|
||||
ThreadSettingsBuildParams {
|
||||
method: "turn/start",
|
||||
disabled_plugin_ids: params.disabled_plugin_ids,
|
||||
environment_override,
|
||||
approval_policy: params.approval_policy,
|
||||
approvals_reviewer: params.approvals_reviewer,
|
||||
@@ -774,6 +776,7 @@ impl TurnRequestProcessor {
|
||||
) -> Result<codex_protocol::protocol::ThreadSettingsOverrides, JSONRPCErrorError> {
|
||||
let ThreadSettingsBuildParams {
|
||||
method,
|
||||
disabled_plugin_ids,
|
||||
environment_override:
|
||||
ThreadEnvironmentOverride {
|
||||
environments,
|
||||
@@ -810,6 +813,7 @@ impl TurnRequestProcessor {
|
||||
};
|
||||
|
||||
let has_any_overrides = has_environment_override
|
||||
|| disabled_plugin_ids.is_some()
|
||||
|| approval_policy.is_some()
|
||||
|| approvals_reviewer.is_some()
|
||||
|| sandbox_policy.is_some()
|
||||
@@ -874,7 +878,7 @@ impl TurnRequestProcessor {
|
||||
if has_any_overrides {
|
||||
thread
|
||||
.preview_thread_settings_overrides(CodexThreadSettingsOverrides {
|
||||
disabled_plugin_ids: None,
|
||||
disabled_plugin_ids: disabled_plugin_ids.clone(),
|
||||
environments: environments.clone(),
|
||||
runtime_workspace_roots: runtime_workspace_roots.clone(),
|
||||
approval_policy,
|
||||
@@ -898,7 +902,7 @@ impl TurnRequestProcessor {
|
||||
}
|
||||
|
||||
Ok(codex_protocol::protocol::ThreadSettingsOverrides {
|
||||
disabled_plugin_ids: None,
|
||||
disabled_plugin_ids,
|
||||
environments,
|
||||
runtime_workspace_roots,
|
||||
profile_workspace_roots,
|
||||
@@ -939,6 +943,7 @@ impl TurnRequestProcessor {
|
||||
thread.as_ref(),
|
||||
ThreadSettingsBuildParams {
|
||||
method: "thread/settings/update",
|
||||
disabled_plugin_ids: params.disabled_plugin_ids,
|
||||
environment_override,
|
||||
approval_policy: params.approval_policy,
|
||||
approvals_reviewer: params.approvals_reviewer,
|
||||
|
||||
@@ -291,6 +291,7 @@ mod tests {
|
||||
|
||||
fn thread_settings(model: &str) -> ThreadSettings {
|
||||
ThreadSettings {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
cwd: AbsolutePathBuf::from_absolute_path("/tmp").expect("absolute path"),
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
|
||||
@@ -5,12 +5,21 @@ use app_test_support::TestAppServer;
|
||||
use app_test_support::create_final_assistant_message_sse_response;
|
||||
use app_test_support::create_mock_responses_server_sequence_unchecked;
|
||||
use app_test_support::write_models_cache;
|
||||
use codex_app_server_protocol::ApprovalsReviewer;
|
||||
use codex_app_server_protocol::AskForApproval;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
use codex_app_server_protocol::JSONRPCError;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::SandboxMode;
|
||||
use codex_app_server_protocol::SandboxPolicy;
|
||||
use codex_app_server_protocol::ThreadForkParams;
|
||||
use codex_app_server_protocol::ThreadForkResponse;
|
||||
use codex_app_server_protocol::ThreadHistoryMode;
|
||||
use codex_app_server_protocol::ThreadListResponse;
|
||||
use codex_app_server_protocol::ThreadReadParams;
|
||||
use codex_app_server_protocol::ThreadReadResponse;
|
||||
use codex_app_server_protocol::ThreadResumeParams;
|
||||
use codex_app_server_protocol::ThreadResumeResponse;
|
||||
use codex_app_server_protocol::ThreadSettingsUpdateParams;
|
||||
use codex_app_server_protocol::ThreadSettingsUpdateResponse;
|
||||
use codex_app_server_protocol::ThreadSettingsUpdatedNotification;
|
||||
@@ -35,10 +44,188 @@ use serde_json::Value;
|
||||
use serde_json::json;
|
||||
use std::time::Duration;
|
||||
use tempfile::TempDir;
|
||||
use test_case::test_case;
|
||||
use tokio::time::timeout;
|
||||
|
||||
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
#[tokio::test]
|
||||
async fn disabled_plugin_ids_replace_preserve_and_clear_without_inference() -> Result<()> {
|
||||
let server = responses::start_mock_server().await;
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), &server.uri())?;
|
||||
let mut mcp = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.build_initialized()
|
||||
.await?;
|
||||
let started = start_thread(&mut mcp).await?;
|
||||
assert_eq!(started.disabled_plugin_ids, Vec::<String>::new());
|
||||
|
||||
for (mut params, expected) in [
|
||||
(
|
||||
json!({"disabledPluginIds": ["slack@openai", "notion@openai"]}),
|
||||
vec!["slack@openai", "notion@openai"],
|
||||
),
|
||||
(
|
||||
json!({"disabledPluginIds": ["slack@openai"]}),
|
||||
vec!["slack@openai"],
|
||||
),
|
||||
(json!({"model": "mock-model-2"}), vec!["slack@openai"]),
|
||||
(
|
||||
json!({"disabledPluginIds": null, "model": "mock-model-3"}),
|
||||
vec!["slack@openai"],
|
||||
),
|
||||
(json!({"disabledPluginIds": []}), vec![]),
|
||||
] {
|
||||
params["threadId"] = json!(started.thread.id);
|
||||
let request_id = mcp
|
||||
.send_raw_request("thread/settings/update", Some(params))
|
||||
.await?;
|
||||
let _: ThreadSettingsUpdateResponse =
|
||||
timeout(DEFAULT_TIMEOUT, mcp.read_response(request_id)).await??;
|
||||
let updated = read_thread_settings_updated(&mut mcp).await?;
|
||||
assert_eq!(updated.thread_settings.disabled_plugin_ids, expected);
|
||||
}
|
||||
assert!(received_response_bodies(&server).await?.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(ThreadHistoryMode::Legacy, true; "cold legacy parent")]
|
||||
#[test_case(ThreadHistoryMode::Legacy, false; "loaded legacy parent")]
|
||||
#[test_case(ThreadHistoryMode::Paginated, true; "cold paginated parent")]
|
||||
#[test_case(ThreadHistoryMode::Paginated, false; "loaded paginated parent")]
|
||||
#[tokio::test]
|
||||
async fn disabled_plugin_ids_restore_from_fork_boundary(
|
||||
history_mode: ThreadHistoryMode,
|
||||
restart_before_fork: bool,
|
||||
) -> Result<()> {
|
||||
let server = create_mock_responses_server_sequence_unchecked(vec![
|
||||
create_final_assistant_message_sse_response("done")?,
|
||||
create_final_assistant_message_sse_response("done again")?,
|
||||
])
|
||||
.await;
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), &server.uri())?;
|
||||
let mut mcp = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.build_initialized()
|
||||
.await?;
|
||||
let started = mcp
|
||||
.start_thread(ThreadStartParams {
|
||||
history_mode: Some(history_mode),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let thread_id = started.thread.id;
|
||||
let initial_selection = vec!["slack@openai".to_string()];
|
||||
let first_turn = mcp
|
||||
.start_turn_and_wait_for_completion(TurnStartParams {
|
||||
thread_id: thread_id.clone(),
|
||||
disabled_plugin_ids: Some(initial_selection.clone()),
|
||||
input: vec![V2UserInput::Text {
|
||||
text: "materialize the thread".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(
|
||||
read_thread_settings_updated(&mut mcp)
|
||||
.await?
|
||||
.thread_settings
|
||||
.disabled_plugin_ids,
|
||||
initial_selection
|
||||
);
|
||||
let second_turn = mcp
|
||||
.start_turn_and_wait_for_completion(TurnStartParams {
|
||||
thread_id: thread_id.clone(),
|
||||
input: vec![V2UserInput::Text {
|
||||
text: "continue beyond the fork cutoff".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
// Clearing after the second turn must not affect forks at an earlier boundary.
|
||||
let current_selection = Vec::<String>::new();
|
||||
send_thread_settings_update(
|
||||
&mut mcp,
|
||||
ThreadSettingsUpdateParams {
|
||||
thread_id: thread_id.clone(),
|
||||
disabled_plugin_ids: Some(current_selection.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
read_thread_settings_updated(&mut mcp)
|
||||
.await?
|
||||
.thread_settings
|
||||
.disabled_plugin_ids,
|
||||
current_selection
|
||||
);
|
||||
if restart_before_fork {
|
||||
mcp.shutdown_gracefully().await?;
|
||||
mcp = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.build_initialized()
|
||||
.await?;
|
||||
}
|
||||
for (params, expected_selection) in [
|
||||
(
|
||||
ThreadForkParams {
|
||||
thread_id: thread_id.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
¤t_selection,
|
||||
),
|
||||
(
|
||||
ThreadForkParams {
|
||||
thread_id: thread_id.clone(),
|
||||
last_turn_id: Some(first_turn.turn.id.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
&initial_selection,
|
||||
),
|
||||
(
|
||||
ThreadForkParams {
|
||||
thread_id: thread_id.clone(),
|
||||
before_turn_id: Some(second_turn.turn.id),
|
||||
..Default::default()
|
||||
},
|
||||
&initial_selection,
|
||||
),
|
||||
(
|
||||
ThreadForkParams {
|
||||
thread_id: thread_id.clone(),
|
||||
last_turn_id: Some(first_turn.turn.id),
|
||||
// Selection restoration must not depend on restoring permissions.
|
||||
approval_policy: Some(AskForApproval::Never),
|
||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||
sandbox: Some(SandboxMode::DangerFullAccess),
|
||||
..Default::default()
|
||||
},
|
||||
&initial_selection,
|
||||
),
|
||||
] {
|
||||
let forked: ThreadForkResponse = mcp
|
||||
.request(|request_id| ClientRequest::ThreadFork { request_id, params })
|
||||
.await?;
|
||||
assert_eq!(&forked.disabled_plugin_ids, expected_selection);
|
||||
}
|
||||
let resumed: ThreadResumeResponse = mcp
|
||||
.request(|request_id| ClientRequest::ThreadResume {
|
||||
request_id,
|
||||
params: ThreadResumeParams {
|
||||
thread_id,
|
||||
..Default::default()
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(resumed.disabled_plugin_ids, current_selection);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_settings_update_emits_notification_and_updates_future_turns() -> Result<()> {
|
||||
let server = create_mock_responses_server_sequence_unchecked(vec![
|
||||
@@ -416,6 +603,7 @@ async fn turn_start_settings_override_emits_thread_settings_updated() -> Result<
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
model: Some("mock-model-3".to_string()),
|
||||
disabled_plugin_ids: Some(vec!["slack@openai".to_string()]),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
@@ -426,6 +614,10 @@ async fn turn_start_settings_override_emits_thread_settings_updated() -> Result<
|
||||
let updated = read_thread_settings_updated(&mut mcp).await?;
|
||||
assert_eq!(updated.thread_id, thread.id);
|
||||
assert_eq!(updated.thread_settings.model, "mock-model-3");
|
||||
assert_eq!(
|
||||
updated.thread_settings.disabled_plugin_ids,
|
||||
vec!["slack@openai"]
|
||||
);
|
||||
|
||||
timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
|
||||
@@ -3232,6 +3232,7 @@ async fn turn_start_explicit_local_environment_updates_legacy_cwd_between_turns(
|
||||
.request(|request_id| ClientRequest::TurnStart {
|
||||
request_id,
|
||||
params: TurnStartParams {
|
||||
disabled_plugin_ids: None,
|
||||
environments: None,
|
||||
thread_id: thread.id.clone(),
|
||||
client_user_message_id: None,
|
||||
@@ -3280,6 +3281,7 @@ async fn turn_start_explicit_local_environment_updates_legacy_cwd_between_turns(
|
||||
.request(|request_id| ClientRequest::TurnStart {
|
||||
request_id,
|
||||
params: TurnStartParams {
|
||||
disabled_plugin_ids: None,
|
||||
environments: Some(vec![TurnEnvironmentParams {
|
||||
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
|
||||
cwd: second_cwd.abs().into(),
|
||||
|
||||
@@ -103,6 +103,7 @@ pub struct ThreadConfigSnapshot {
|
||||
pub parent_thread_id: Option<ThreadId>,
|
||||
pub thread_source: Option<ThreadSource>,
|
||||
pub originator: String,
|
||||
pub disabled_plugin_ids: Vec<String>,
|
||||
}
|
||||
|
||||
impl ThreadConfigSnapshot {
|
||||
|
||||
@@ -1456,6 +1456,10 @@ url = "ws://127.0.0.1:8765"
|
||||
.with_span_events(FmtSpan::NEW)
|
||||
.with_writer(MockWriter::new(buffer))
|
||||
.finish();
|
||||
// Avoid tracing-core's single-dispatch path caching no interest when another
|
||||
// test first reaches these shared callsites without a subscriber.
|
||||
let _interest_cache_guard =
|
||||
tracing::Dispatch::new(tracing::subscriber::NoSubscriber::default());
|
||||
let _subscriber_guard = tracing::subscriber::set_default(subscriber);
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0")
|
||||
@@ -1487,6 +1491,13 @@ url = "ws://127.0.0.1:8765"
|
||||
));
|
||||
environments
|
||||
.update_selections(std::slice::from_ref(&selection), &test_environment_config());
|
||||
// Exercise first-use callsite registration from a thread without a subscriber.
|
||||
std::thread::spawn({
|
||||
let environments = Arc::clone(&environments);
|
||||
move || assert!(environments.snapshot().now_or_never().is_none())
|
||||
})
|
||||
.join()
|
||||
.expect("unsubscribed snapshot");
|
||||
let snapshot_task = tokio::spawn({
|
||||
let environments = Arc::clone(&environments);
|
||||
async move { environments.snapshot().await }
|
||||
|
||||
@@ -291,6 +291,7 @@ impl SessionConfiguration {
|
||||
parent_thread_id: self.parent_thread_id,
|
||||
thread_source: self.thread_source.clone(),
|
||||
originator: self.originator.clone(),
|
||||
disabled_plugin_ids: self.disabled_plugin_ids.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1453,18 +1453,6 @@ impl ThreadManager {
|
||||
options.thread_instructions_provider.clone(),
|
||||
)
|
||||
.await;
|
||||
// Capture the source's settings before truncating its model history.
|
||||
options.disabled_plugin_ids = Some(options.disabled_plugin_ids.unwrap_or_else(|| {
|
||||
source_thread_id
|
||||
.and_then(|thread_id| {
|
||||
codex_history::latest_disabled_plugin_ids(
|
||||
history.get_rollout_items(),
|
||||
thread_id,
|
||||
)
|
||||
})
|
||||
.map(<[String]>::to_vec)
|
||||
.unwrap_or_default()
|
||||
}));
|
||||
let multi_agent_version = self
|
||||
.state
|
||||
.effective_multi_agent_version_for_spawn(
|
||||
|
||||
@@ -13,12 +13,14 @@ use codex_protocol::mcp::ClientMcpExtensions;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::ThreadHistoryMode;
|
||||
use codex_protocol::protocol::ThreadSettingsAppliedEvent;
|
||||
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||
use codex_protocol::protocol::ThreadSettingsSnapshot;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use core_test_support::responses::ev_completed;
|
||||
use core_test_support::responses::ev_response_created;
|
||||
use core_test_support::responses::sse;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use core_test_support::submit_thread_settings;
|
||||
use core_test_support::test_codex::test_codex;
|
||||
use core_test_support::wait_for_event;
|
||||
use wiremock::Mock;
|
||||
@@ -166,6 +168,79 @@ fn thread_settings_applied_item(
|
||||
))
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn fork_thread_restores_history_selection_and_preserves_explicit_clear() -> anyhow::Result<()>
|
||||
{
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = MockServer::start().await;
|
||||
let mut builder = test_codex();
|
||||
let test = builder.build_with_auto_env(&server).await?;
|
||||
let selected = vec!["slack@openai".to_string()];
|
||||
submit_thread_settings(
|
||||
&test.codex,
|
||||
ThreadSettingsOverrides {
|
||||
disabled_plugin_ids: Some(selected.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let history = InitialHistory::Resumed(ResumedHistory {
|
||||
conversation_id: test.session_configured.thread_id,
|
||||
history: Arc::new(vec![thread_settings_applied_item(
|
||||
test.session_configured.thread_id,
|
||||
test.codex.thread_settings_snapshot().await,
|
||||
)]),
|
||||
rollout_path: None,
|
||||
});
|
||||
submit_thread_settings(
|
||||
&test.codex,
|
||||
ThreadSettingsOverrides {
|
||||
disabled_plugin_ids: Some(Vec::new()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let inherited = test
|
||||
.thread_manager
|
||||
.fork_thread_from_history(
|
||||
ForkSnapshot::Interrupted,
|
||||
codex_core::StartThreadOptions::new(test.config.clone()),
|
||||
history.clone(),
|
||||
)
|
||||
.await?;
|
||||
pretty_assertions::assert_eq!(
|
||||
inherited
|
||||
.thread
|
||||
.thread_settings_snapshot()
|
||||
.await
|
||||
.disabled_plugin_ids,
|
||||
selected
|
||||
);
|
||||
|
||||
let explicit = test
|
||||
.thread_manager
|
||||
.fork_thread_from_history(
|
||||
ForkSnapshot::Interrupted,
|
||||
codex_core::StartThreadOptions {
|
||||
disabled_plugin_ids: Some(Vec::new()),
|
||||
..codex_core::StartThreadOptions::new(test.config.clone())
|
||||
},
|
||||
history,
|
||||
)
|
||||
.await?;
|
||||
pretty_assertions::assert_eq!(
|
||||
explicit
|
||||
.thread
|
||||
.thread_settings_snapshot()
|
||||
.await
|
||||
.disabled_plugin_ids,
|
||||
Vec::<String>::new()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn fork_thread_from_history_does_not_require_source_rollout_path() {
|
||||
assert_copied_fork_persists_inherited_history(ThreadHistoryMode::Legacy).await;
|
||||
|
||||
@@ -1139,6 +1139,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
|
||||
ClientRequest::TurnStart {
|
||||
request_id: request_ids.next(),
|
||||
params: TurnStartParams {
|
||||
disabled_plugin_ids: None,
|
||||
thread_id: primary_thread_id_for_span.clone(),
|
||||
turn_trigger: None,
|
||||
client_user_message_id: None,
|
||||
|
||||
@@ -843,6 +843,7 @@ async fn session_configured_from_thread_response_preserves_parent_thread_id() {
|
||||
|
||||
fn sample_thread_start_response() -> ThreadStartResponse {
|
||||
ThreadStartResponse {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
thread: codex_app_server_protocol::Thread {
|
||||
originator: None,
|
||||
environments: None,
|
||||
|
||||
@@ -248,6 +248,7 @@ mod tests {
|
||||
|
||||
fn test_thread_settings() -> ThreadSettings {
|
||||
ThreadSettings {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
cwd: test_path_buf("/tmp/thread-settings").abs(),
|
||||
approval_policy: codex_app_server_protocol::AskForApproval::Never,
|
||||
approvals_reviewer: codex_app_server_protocol::ApprovalsReviewer::User,
|
||||
|
||||
@@ -9236,6 +9236,7 @@ async fn inactive_thread_settings_notification_updates_cached_collaboration_mode
|
||||
let notification = ThreadSettingsUpdatedNotification {
|
||||
thread_id: inactive_thread_id.to_string(),
|
||||
thread_settings: ThreadSettings {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
cwd: test_absolute_path("/tmp/thread-settings"),
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
approvals_reviewer: codex_app_server_protocol::ApprovalsReviewer::AutoReview,
|
||||
|
||||
@@ -1336,6 +1336,7 @@ impl AppServerSession {
|
||||
.request_typed(ClientRequest::TurnStart {
|
||||
request_id,
|
||||
params: TurnStartParams {
|
||||
disabled_plugin_ids: None,
|
||||
thread_id: thread_id.to_string(),
|
||||
turn_trigger: None,
|
||||
client_user_message_id: Some(client_user_message_id),
|
||||
@@ -3877,6 +3878,7 @@ mod tests {
|
||||
let forked_from_id = ThreadId::new();
|
||||
let read_only_profile = PermissionProfile::read_only();
|
||||
let response = ThreadResumeResponse {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
thread: codex_app_server_protocol::Thread {
|
||||
originator: None,
|
||||
environments: None,
|
||||
|
||||
@@ -13,6 +13,7 @@ fn thread_settings_for_test(
|
||||
codex_app_server_protocol::ThreadSettingsUpdatedNotification {
|
||||
thread_id: thread_id.to_string(),
|
||||
thread_settings: codex_app_server_protocol::ThreadSettings {
|
||||
disabled_plugin_ids: Vec::new(),
|
||||
cwd: test_path_buf("/tmp/thread-settings").abs(),
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
approvals_reviewer: codex_app_server_protocol::ApprovalsReviewer::AutoReview,
|
||||
|
||||
@@ -9899,6 +9899,13 @@ class ThreadSettings(BaseModel):
|
||||
approvals_reviewer: Annotated[ApprovalsReviewer, Field(alias="approvalsReviewer")]
|
||||
collaboration_mode: Annotated[CollaborationMode, Field(alias="collaborationMode")]
|
||||
cwd: AbsolutePathBuf
|
||||
disabled_plugin_ids: Annotated[
|
||||
list[str] | None,
|
||||
Field(
|
||||
alias="disabledPluginIds",
|
||||
description="Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
),
|
||||
] = []
|
||||
effort: ReasoningEffort | None = None
|
||||
model: str
|
||||
model_provider: Annotated[str, Field(alias="modelProvider")]
|
||||
@@ -11785,6 +11792,13 @@ class ThreadForkResponse(BaseModel):
|
||||
),
|
||||
]
|
||||
cwd: AbsolutePathBuf
|
||||
disabled_plugin_ids: Annotated[
|
||||
list[str] | None,
|
||||
Field(
|
||||
alias="disabledPluginIds",
|
||||
description="Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
),
|
||||
] = []
|
||||
instruction_sources: Annotated[
|
||||
list[LegacyAppPathString] | None,
|
||||
Field(
|
||||
@@ -11853,6 +11867,13 @@ class ThreadResumeResponse(BaseModel):
|
||||
),
|
||||
]
|
||||
cwd: AbsolutePathBuf
|
||||
disabled_plugin_ids: Annotated[
|
||||
list[str] | None,
|
||||
Field(
|
||||
alias="disabledPluginIds",
|
||||
description="Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
),
|
||||
] = []
|
||||
instruction_sources: Annotated[
|
||||
list[LegacyAppPathString] | None,
|
||||
Field(
|
||||
@@ -11946,6 +11967,13 @@ class ThreadStartResponse(BaseModel):
|
||||
),
|
||||
]
|
||||
cwd: AbsolutePathBuf
|
||||
disabled_plugin_ids: Annotated[
|
||||
list[str] | None,
|
||||
Field(
|
||||
alias="disabledPluginIds",
|
||||
description="Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.",
|
||||
),
|
||||
] = []
|
||||
instruction_sources: Annotated[
|
||||
list[LegacyAppPathString] | None,
|
||||
Field(
|
||||
@@ -12024,6 +12052,13 @@ class TurnStartParams(BaseModel):
|
||||
str | None,
|
||||
Field(description="Override the working directory for this turn and subsequent turns."),
|
||||
] = None
|
||||
disabled_plugin_ids: Annotated[
|
||||
list[str] | None,
|
||||
Field(
|
||||
alias="disabledPluginIds",
|
||||
description="Replace this thread's disabled plugin IDs. Omitted/null preserves the list; [] clears it.",
|
||||
),
|
||||
] = None
|
||||
effort: Annotated[
|
||||
ReasoningEffort | None,
|
||||
Field(description="Override the reasoning effort for this turn and subsequent turns."),
|
||||
|
||||
Reference in New Issue
Block a user