mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
Disable multi-agent tools in side conversations
This commit is contained in:
+3
-2
@@ -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"
|
||||
],
|
||||
|
||||
+4
-3
@@ -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",
|
||||
|
||||
+4
-3
@@ -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"
|
||||
],
|
||||
|
||||
+2
-1
@@ -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"
|
||||
],
|
||||
|
||||
+2
-1
@@ -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
@@ -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>,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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(()));
|
||||
|
||||
@@ -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}")),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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{}",
|
||||
|
||||
Reference in New Issue
Block a user