Disable multi-agent tools in side conversations

This commit is contained in:
jif-oai
2026-06-09 13:24:53 +01:00
parent 1547785657
commit 68738579eb
30 changed files with 204 additions and 32 deletions
+3 -2
View File
@@ -3237,7 +3237,7 @@
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this forked thread."
"description": "Optional client-supplied source classification for this forked thread. Threads marked as `side` do not receive multi-agent collaboration tools."
}
},
"required": [
@@ -3797,6 +3797,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
@@ -3934,7 +3935,7 @@
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this thread."
"description": "Optional client-supplied source classification for this thread. Threads marked as `side` do not receive multi-agent collaboration tools."
}
},
"type": "object"
@@ -3463,7 +3463,7 @@
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
"description": "Optional source classification for this thread. `side` threads do not receive multi-agent collaboration tools."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
@@ -4617,6 +4617,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
@@ -15879,7 +15879,7 @@
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
"description": "Optional source classification for this thread. `side` threads do not receive multi-agent collaboration tools."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
@@ -16095,7 +16095,7 @@
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this forked thread."
"description": "Optional client-supplied source classification for this forked thread. Threads marked as `side` do not receive multi-agent collaboration tools."
}
},
"required": [
@@ -18059,6 +18059,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
@@ -18197,7 +18198,7 @@
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this thread."
"description": "Optional client-supplied source classification for this thread. Threads marked as `side` do not receive multi-agent collaboration tools."
}
},
"title": "ThreadStartParams",
@@ -13696,7 +13696,7 @@
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
"description": "Optional source classification for this thread. `side` threads do not receive multi-agent collaboration tools."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
@@ -13912,7 +13912,7 @@
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this forked thread."
"description": "Optional client-supplied source classification for this forked thread. Threads marked as `side` do not receive multi-agent collaboration tools."
}
},
"required": [
@@ -15876,6 +15876,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
@@ -16014,7 +16015,7 @@
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this thread."
"description": "Optional client-supplied source classification for this thread. Threads marked as `side` do not receive multi-agent collaboration tools."
}
},
"title": "ThreadStartParams",
@@ -75,6 +75,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
@@ -173,7 +174,7 @@
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this forked thread."
"description": "Optional client-supplied source classification for this forked thread. Threads marked as `side` do not receive multi-agent collaboration tools."
}
},
"required": [
@@ -1084,7 +1084,7 @@
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
"description": "Optional source classification for this thread. `side` threads do not receive multi-agent collaboration tools."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
@@ -1832,6 +1832,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
@@ -899,7 +899,7 @@
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
"description": "Optional source classification for this thread. `side` threads do not receive multi-agent collaboration tools."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
@@ -1647,6 +1647,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
@@ -899,7 +899,7 @@
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
"description": "Optional source classification for this thread. `side` threads do not receive multi-agent collaboration tools."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
@@ -1647,6 +1647,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
@@ -899,7 +899,7 @@
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
"description": "Optional source classification for this thread. `side` threads do not receive multi-agent collaboration tools."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
@@ -1647,6 +1647,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
@@ -1084,7 +1084,7 @@
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
"description": "Optional source classification for this thread. `side` threads do not receive multi-agent collaboration tools."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
@@ -1832,6 +1832,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
@@ -899,7 +899,7 @@
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
"description": "Optional source classification for this thread. `side` threads do not receive multi-agent collaboration tools."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
@@ -1647,6 +1647,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
@@ -109,6 +109,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
@@ -253,7 +254,7 @@
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this thread."
"description": "Optional client-supplied source classification for this thread. Threads marked as `side` do not receive multi-agent collaboration tools."
}
},
"title": "ThreadStartParams",
@@ -1084,7 +1084,7 @@
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
"description": "Optional source classification for this thread. `side` threads do not receive multi-agent collaboration tools."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
@@ -1832,6 +1832,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
@@ -899,7 +899,7 @@
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
"description": "Optional source classification for this thread. `side` threads do not receive multi-agent collaboration tools."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
@@ -1647,6 +1647,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
@@ -899,7 +899,7 @@
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
"description": "Optional source classification for this thread. `side` threads do not receive multi-agent collaboration tools."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
@@ -1647,6 +1647,7 @@
"ThreadSource": {
"enum": [
"user",
"side",
"subagent",
"memory_consolidation"
],
+2 -1
View File
@@ -62,7 +62,8 @@ cliVersion: string,
*/
source: SessionSource,
/**
* Optional analytics source classification for this thread.
* Optional source classification for this thread. `side` threads do not receive multi-agent
* collaboration tools.
*/
threadSource: ThreadSource | null,
/**
@@ -25,6 +25,7 @@ model?: string | null, modelProvider?: string | null, serviceTier?: string | nul
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, ephemeral?: boolean, /**
* Optional client-supplied analytics source classification for this forked thread.
* Optional client-supplied source classification for this forked thread. Threads marked as
* `side` do not receive multi-agent collaboration tools.
*/
threadSource?: ThreadSource | null};
@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ThreadSource = "user" | "subagent" | "memory_consolidation";
export type ThreadSource = "user" | "side" | "subagent" | "memory_consolidation";
@@ -14,6 +14,7 @@ export type ThreadStartParams = {model?: string | null, modelProvider?: string |
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, sessionStartSource?: ThreadStartSource | null, /**
* Optional client-supplied analytics source classification for this thread.
* Optional client-supplied source classification for this thread. Threads marked as `side`
* do not receive multi-agent collaboration tools.
*/
threadSource?: ThreadSource | null};
@@ -138,7 +138,8 @@ pub struct ThreadStartParams {
pub ephemeral: Option<bool>,
#[ts(optional = nullable)]
pub session_start_source: Option<ThreadStartSource>,
/// Optional client-supplied analytics source classification for this thread.
/// Optional client-supplied source classification for this thread. Threads marked as `side`
/// do not receive multi-agent collaboration tools.
#[ts(optional = nullable)]
pub thread_source: Option<ThreadSource>,
/// Optional sticky environments for this thread.
@@ -533,7 +534,8 @@ pub struct ThreadForkParams {
pub developer_instructions: Option<String>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub ephemeral: bool,
/// Optional client-supplied analytics source classification for this forked thread.
/// Optional client-supplied source classification for this forked thread. Threads marked as
/// `side` do not receive multi-agent collaboration tools.
#[ts(optional = nullable)]
pub thread_source: Option<ThreadSource>,
/// When true, return only thread metadata and live fork state without
@@ -66,6 +66,7 @@ impl From<SessionSource> for CoreSessionSource {
#[ts(rename_all = "snake_case", export_to = "v2/")]
pub enum ThreadSource {
User,
Side,
Subagent,
MemoryConsolidation,
}
@@ -74,6 +75,7 @@ impl From<CoreThreadSource> for ThreadSource {
fn from(value: CoreThreadSource) -> Self {
match value {
CoreThreadSource::User => ThreadSource::User,
CoreThreadSource::Side => ThreadSource::Side,
CoreThreadSource::Subagent => ThreadSource::Subagent,
CoreThreadSource::MemoryConsolidation => ThreadSource::MemoryConsolidation,
}
@@ -84,6 +86,7 @@ impl From<ThreadSource> for CoreThreadSource {
fn from(value: ThreadSource) -> Self {
match value {
ThreadSource::User => CoreThreadSource::User,
ThreadSource::Side => CoreThreadSource::Side,
ThreadSource::Subagent => CoreThreadSource::Subagent,
ThreadSource::MemoryConsolidation => CoreThreadSource::MemoryConsolidation,
}
@@ -132,7 +135,8 @@ pub struct Thread {
pub cli_version: String,
/// Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).
pub source: SessionSource,
/// Optional analytics source classification for this thread.
/// Optional source classification for this thread. `side` threads do not receive multi-agent
/// collaboration tools.
pub thread_source: Option<ThreadSource>,
/// Optional random unique nickname assigned to an AgentControl-spawned sub-agent.
pub agent_nickname: Option<String>,
+1 -1
View File
@@ -132,7 +132,7 @@ Example with notification opt-out:
- `thread/start` — create a new thread; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for that thread. When the request includes a `cwd` and the resolved sandbox is `workspace-write` or full access, app-server also marks that project as trusted in the user `config.toml`. Pass `sessionStartSource: "clear"` when starting a replacement thread after clearing the current session so `SessionStart` hooks receive `source: "clear"` instead of the default `"startup"`. Experimental `runtimeWorkspaceRoots` replaces the thread-scoped runtime workspace roots used to materialize `:workspace_roots`; paths must be absolute. For permissions, prefer experimental `permissions` profile selection by id; the legacy `sandbox` shorthand is still accepted but cannot be combined with `permissions`. Experimental `environments` selects the sticky execution environments for turns on the thread; omit it to use the server default, pass `[]` to disable environments, or pass explicit environment ids with per-environment `cwd`.
- `thread/resume` — reopen an existing thread by id so subsequent `turn/start` calls append to it. Accepts the same permission override rules as `thread/start`.
- `thread/fork` — fork an existing thread into a new thread id by copying the stored history; if the source thread is currently mid-turn, the fork records the same interruption marker as `turn/interrupt` instead of inheriting an unmarked partial turn suffix. The returned `thread.forkedFromId` points at the source thread when known. Accepts `ephemeral: true` for an in-memory temporary fork, emits `thread/started` (including the current `thread.status`), and auto-subscribes you to turn/item events for the new thread. Experimental clients can pass `excludeTurns: true` when they plan to page fork history via `thread/turns/list` instead of receiving the full turn array immediately. Accepts the same permission override rules as `thread/start`.
- `thread/fork` — fork an existing thread into a new thread id by copying the stored history; if the source thread is currently mid-turn, the fork records the same interruption marker as `turn/interrupt` instead of inheriting an unmarked partial turn suffix. The returned `thread.forkedFromId` points at the source thread when known. Accepts `ephemeral: true` for an in-memory temporary fork, emits `thread/started` (including the current `thread.status`), and auto-subscribes you to turn/item events for the new thread. Set `threadSource` to `side` for an isolated side conversation; side threads do not receive multi-agent collaboration tools. Experimental clients can pass `excludeTurns: true` when they plan to page fork history via `thread/turns/list` instead of receiving the full turn array immediately. Accepts the same permission override rules as `thread/start`.
- `thread/start`, `thread/resume`, and `thread/fork` responses include the legacy `sandbox` compatibility projection. Experimental clients can read `runtimeWorkspaceRoots` for the thread-scoped runtime roots and `activePermissionProfile` for the named or implicit built-in profile identity/provenance when known.
- `thread/list` — page through stored rollouts; supports cursor-based pagination and optional `modelProviders`, `sourceKinds`, `archived`, `cwd`, and `searchTerm` filters. Each returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded. Subagent threads also include `parentThreadId` when the immediate control/spawn parent is known.
- `thread/loaded/list` — list the thread ids currently loaded in memory.
+4
View File
@@ -64,6 +64,7 @@ use codex_protocol::openai_models::ToolMode;
use codex_protocol::protocol::MultiAgentVersion;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use codex_protocol::protocol::ThreadSource;
use codex_tools::DiscoverableTool;
use codex_tools::ResponsesApiNamespace;
use codex_tools::ResponsesApiNamespaceTool;
@@ -302,6 +303,9 @@ fn multi_agent_v2_enabled(turn_context: &TurnContext) -> bool {
}
fn collab_tools_enabled(turn_context: &TurnContext) -> bool {
if turn_context.thread_source == Some(ThreadSource::Side) {
return false;
}
match turn_context.multi_agent_version {
MultiAgentVersion::Disabled => false,
MultiAgentVersion::V1 => !exceeds_thread_spawn_depth_limit(
@@ -17,6 +17,7 @@ use codex_protocol::openai_models::ToolMode;
use codex_protocol::openai_models::WebSearchToolType;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use codex_protocol::protocol::ThreadSource;
use codex_tools::DiscoverablePluginInfo;
use codex_tools::DiscoverableTool;
use codex_tools::ResponsesApiNamespaceTool;
@@ -1043,6 +1044,42 @@ async fn multi_agent_feature_selects_one_agent_tool_family() {
);
}
#[tokio::test]
async fn side_threads_do_not_expose_multi_agent_tools() {
let v1 = probe(|turn| {
set_features(turn, &[Feature::Collab, Feature::SpawnCsv]);
set_feature(turn, Feature::MultiAgentV2, /*enabled*/ false);
turn.thread_source = Some(ThreadSource::Side);
})
.await;
v1.assert_visible_lacks(&[MULTI_AGENT_V1_NAMESPACE, "spawn_agents_on_csv"]);
v1.assert_registered_lacks(&[
&ToolName::namespaced(MULTI_AGENT_V1_NAMESPACE, "spawn_agent").to_string(),
&ToolName::namespaced(MULTI_AGENT_V1_NAMESPACE, "send_input").to_string(),
&ToolName::namespaced(MULTI_AGENT_V1_NAMESPACE, "resume_agent").to_string(),
&ToolName::namespaced(MULTI_AGENT_V1_NAMESPACE, "wait_agent").to_string(),
&ToolName::namespaced(MULTI_AGENT_V1_NAMESPACE, "close_agent").to_string(),
"spawn_agents_on_csv",
]);
let v2 = probe(|turn| {
set_features(turn, &[Feature::MultiAgentV2, Feature::SpawnCsv]);
turn.thread_source = Some(ThreadSource::Side);
})
.await;
let v2_tools = [
"spawn_agent",
"send_message",
"followup_task",
"wait_agent",
"interrupt_agent",
"list_agents",
"spawn_agents_on_csv",
];
v2.assert_visible_lacks(&v2_tools);
v2.assert_registered_lacks(&v2_tools);
}
#[tokio::test]
async fn multi_agent_v2_message_schemas_are_encrypted() {
let plan = probe(|turn| {
+87
View File
@@ -7,6 +7,7 @@ use std::time::Instant;
use anyhow::Context;
use anyhow::Result;
use codex_core::StartThreadOptions;
use codex_core::sandboxing::SandboxPermissions;
use codex_features::Feature;
use codex_protocol::models::PermissionProfile;
@@ -16,6 +17,11 @@ use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::InitialHistory;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::ThreadSource;
use codex_protocol::user_input::UserInput;
use core_test_support::assert_regex_match;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
@@ -30,6 +36,8 @@ use core_test_support::skip_if_no_network;
use core_test_support::skip_if_sandbox;
use core_test_support::test_codex::local;
use core_test_support::test_codex::test_codex;
use core_test_support::wait_for_event;
use pretty_assertions::assert_eq;
use regex_lite::Regex;
use serde_json::Value;
use serde_json::json;
@@ -51,6 +59,85 @@ fn tool_names(body: &Value) -> Vec<String> {
.unwrap_or_default()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn side_threads_omit_multi_agent_tools_from_requests() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let response_mock = mount_sse_once(
&server,
sse(vec![
ev_response_created("resp-1"),
ev_assistant_message("msg-1", "done"),
ev_completed("resp-1"),
]),
)
.await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::MultiAgentV2)
.expect("multi-agent v2 should enable for test");
config
.features
.enable(Feature::SpawnCsv)
.expect("spawn CSV should enable for test");
});
let test = builder.build(&server).await?;
let side_thread = test
.thread_manager
.start_thread_with_options(StartThreadOptions {
config: test.config.clone(),
initial_history: InitialHistory::New,
session_source: None,
thread_source: Some(ThreadSource::Side),
dynamic_tools: Vec::new(),
metrics_service_name: None,
parent_trace: None,
environments: Vec::new(),
})
.await?;
side_thread
.thread
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "which tools are available?".to_string(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
additional_context: Default::default(),
thread_settings: Default::default(),
})
.await?;
wait_for_event(&side_thread.thread, |event| {
matches!(event, EventMsg::TurnComplete(_))
})
.await;
let tools = tool_names(&response_mock.single_request().body_json());
assert!(tools.contains(&"update_plan".to_string()));
let present_multi_agent_tools = tools
.into_iter()
.filter(|tool| {
matches!(
tool.as_str(),
"spawn_agent"
| "send_message"
| "followup_task"
| "wait_agent"
| "interrupt_agent"
| "list_agents"
| "spawn_agents_on_csv"
)
})
.collect::<Vec<_>>();
assert_eq!(present_multi_agent_tools, Vec::<String>::new());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn empty_turn_environments_omits_environment_backed_tools() -> Result<()> {
skip_if_no_network!(Ok(()));
+3
View File
@@ -2534,6 +2534,7 @@ pub enum SessionSource {
#[ts(rename_all = "snake_case")]
pub enum ThreadSource {
User,
Side,
Subagent,
MemoryConsolidation,
}
@@ -2542,6 +2543,7 @@ impl ThreadSource {
pub fn as_str(self) -> &'static str {
match self {
ThreadSource::User => "user",
ThreadSource::Side => "side",
ThreadSource::Subagent => "subagent",
ThreadSource::MemoryConsolidation => "memory_consolidation",
}
@@ -2560,6 +2562,7 @@ impl FromStr for ThreadSource {
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"user" => Ok(ThreadSource::User),
"side" => Ok(ThreadSource::Side),
"subagent" => Ok(ThreadSource::Subagent),
"memory_consolidation" => Ok(ThreadSource::MemoryConsolidation),
other => Err(format!("unknown thread source: {other}")),
+2 -1
View File
@@ -126,6 +126,7 @@ use codex_app_server_protocol::ThreadItem;
use codex_app_server_protocol::ThreadLoadedListParams;
use codex_app_server_protocol::ThreadMemoryMode;
use codex_app_server_protocol::ThreadRollbackResponse;
use codex_app_server_protocol::ThreadSource;
use codex_app_server_protocol::ThreadStartSource;
use codex_app_server_protocol::Turn;
use codex_app_server_protocol::TurnError as AppServerTurnError;
@@ -960,7 +961,7 @@ impl App {
&[("source", "cli_subcommand")],
);
let forked = app_server
.fork_thread(config.clone(), target_session.thread_id)
.fork_thread(config.clone(), target_session.thread_id, ThreadSource::User)
.await
.map_err(|err| session_start_error("fork", &target_session, err))?;
let init = crate::chatwidget::ChatWidgetInit {
+4 -1
View File
@@ -144,7 +144,10 @@ impl App {
if let Some(thread_id) = self.chat_widget.thread_id() {
self.refresh_in_memory_config_from_disk_best_effort("forking the thread")
.await;
match app_server.fork_thread(self.config.clone(), thread_id).await {
match app_server
.fork_thread(self.config.clone(), thread_id, ThreadSource::User)
.await
{
Ok(forked) => {
self.shutdown_current_thread(app_server).await;
match self
+4 -1
View File
@@ -564,7 +564,10 @@ impl App {
.await;
let fork_config = self.side_fork_config();
match app_server.fork_thread(fork_config, parent_thread_id).await {
match app_server
.fork_thread(fork_config, parent_thread_id, ThreadSource::Side)
.await
{
Ok(forked) => {
let child_thread_id = forked.session.thread_id;
let channel = self.ensure_thread_channel(child_thread_id);
+11 -1
View File
@@ -445,6 +445,7 @@ impl AppServerSession {
&mut self,
config: Config,
thread_id: ThreadId,
thread_source: ThreadSource,
) -> Result<AppServerStartedThread> {
let request_id = self.next_request_id();
let session_config = self.session_config_with_effective_service_tier(&config);
@@ -457,6 +458,7 @@ impl AppServerSession {
thread_id,
self.thread_params_mode(),
self.remote_cwd_override.as_deref(),
thread_source,
),
})
.await
@@ -1461,6 +1463,7 @@ fn thread_fork_params_from_config(
thread_id: ThreadId,
thread_params_mode: ThreadParamsMode,
remote_cwd_override: Option<&std::path::Path>,
thread_source: ThreadSource,
) -> ThreadForkParams {
let permissions = permissions_selection_from_config(&config, thread_params_mode);
let sandbox = permissions
@@ -1490,7 +1493,7 @@ fn thread_fork_params_from_config(
config.developer_instructions.clone(),
),
ephemeral: config.ephemeral,
thread_source: Some(ThreadSource::User),
thread_source: Some(thread_source),
..ThreadForkParams::default()
}
}
@@ -2016,6 +2019,7 @@ mod tests {
thread_id,
ThreadParamsMode::Remote,
/*remote_cwd_override*/ None,
ThreadSource::User,
);
assert_eq!(start.cwd, None);
@@ -2133,6 +2137,7 @@ mod tests {
thread_id,
ThreadParamsMode::Remote,
Some(remote_cwd.as_path()),
ThreadSource::User,
);
assert_eq!(start.cwd.as_deref(), Some("repo/on/server"));
@@ -2184,6 +2189,7 @@ mod tests {
thread_id,
ThreadParamsMode::Embedded,
/*remote_cwd_override*/ None,
ThreadSource::User,
);
let expected_service_tier = Some(Some(ServiceTier::Fast.request_value().to_string()));
@@ -2238,6 +2244,7 @@ mod tests {
thread_id,
ThreadParamsMode::Embedded,
/*remote_cwd_override*/ None,
ThreadSource::Side,
);
assert_eq!(params.base_instructions.as_deref(), Some("Base override."));
@@ -2245,6 +2252,7 @@ mod tests {
params.developer_instructions.as_deref(),
Some("Developer override.")
);
assert_eq!(params.thread_source, Some(ThreadSource::Side));
}
#[tokio::test]
@@ -2271,6 +2279,7 @@ mod tests {
thread_id,
ThreadParamsMode::Embedded,
/*remote_cwd_override*/ None,
ThreadSource::User,
);
assert_eq!(control_start.developer_instructions, None);
@@ -2300,6 +2309,7 @@ mod tests {
thread_id,
ThreadParamsMode::Embedded,
/*remote_cwd_override*/ None,
ThreadSource::User,
);
let expected = format!(
"Developer override.\n\n{}",