Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion codex-rs/app-server-protocol/schema/json/ClientRequest.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions codex-rs/app-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Example with notification opt-out:

## API Overview

- `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 `multiAgentMode` selects the initial thread mode and defaults to `explicitRequestOnly` when omitted; use `none` to keep multi-agent tools available without injecting mode instructions. 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`. Experimental `selectedCapabilityRoots` selects environment-owned plugin or standalone-skill roots. Skills found below those roots are listed and read through the owning environment. Stdio MCP servers declared by selected plugins are also started in that environment; HTTP MCP declarations remain inactive.
- `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 `multiAgentMode` selects the initial thread mode and defaults to `explicitRequestOnly` when omitted; use `none` to keep multi-agent tools available without injecting mode instructions. 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`. Experimental `selectedCapabilityRoots` selects environment-owned plugin or standalone-skill roots using environment-native absolute paths. Skills found below those roots are listed and read through the owning environment. Stdio MCP servers declared by selected plugins are also started in that environment; HTTP MCP declarations remain inactive.
- `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`. Multi-agent mode restores the last effective mode from rollout history when available; clients can select another mode on the first `turn/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/start`, `thread/resume`, and `thread/fork` responses include the legacy `sandbox` compatibility projection. `instructionSources` lists loaded instruction files using each source environment's native absolute path syntax, including files loaded from remote environments. 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. Their experimental `multiAgentMode` field, and the corresponding thread setting, report the thread's current mode. Turn construction separately determines whether that mode is applicable to the selected model and runtime configuration.
Expand Down Expand Up @@ -267,7 +267,6 @@ Start a fresh thread when you need a new Codex conversation.
"location": {
"type": "environment",
"environmentId": "workspace",
// Opaque to app-server; interpreted in the selected environment.
"path": "/opt/cca/plugins/github"
}
}
Expand Down
3 changes: 2 additions & 1 deletion codex-rs/app-server/tests/suite/v2/executor_mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadStartResponse;
use codex_app_server_protocol::TurnStartParams;
use codex_app_server_protocol::UserInput;
use codex_utils_path_uri::PathUri;
use core_test_support::responses;
use core_test_support::stdio_server_bin;
use pretty_assertions::assert_eq;
Expand Down Expand Up @@ -92,7 +93,7 @@ args = ["exec-server", "--listen", "stdio"]
id: "executor-demo@1".to_string(),
location: CapabilityRootLocation::Environment {
environment_id: EXECUTOR_ID.to_string(),
path: plugin.path().to_string_lossy().into_owned(),
path: PathUri::from_host_native_path(plugin.path())?,
},
}]),
)
Expand Down
3 changes: 2 additions & 1 deletion codex-rs/app-server/tests/suite/v2/executor_skills.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadStartResponse;
use codex_app_server_protocol::TurnStartParams;
use codex_app_server_protocol::UserInput;
use codex_utils_path_uri::PathUri;
use core_test_support::responses;
use tempfile::TempDir;
use tokio::time::timeout;
Expand Down Expand Up @@ -90,7 +91,7 @@ stream_max_retries = 0
id: "demo-plugin@1".to_string(),
location: CapabilityRootLocation::Environment {
environment_id: "local".to_string(),
path: plugin_dir.path().to_string_lossy().into_owned(),
path: PathUri::from_host_native_path(plugin_dir.path())?,
},
}]),
..Default::default()
Expand Down
81 changes: 74 additions & 7 deletions codex-rs/core-plugins/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,11 @@ mod tests {
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
use codex_plugin::PluginProvider;
use codex_plugin::ResolvedPlugin;
use codex_plugin::manifest::PluginManifest as GenericPluginManifest;
use codex_plugin::manifest::PluginManifestHooks;
use codex_plugin::manifest::PluginManifestInterface;
use codex_plugin::manifest::PluginManifestMcpServers;
use codex_plugin::manifest::PluginManifestPaths;
use codex_protocol::capabilities::CapabilityRootLocation;
use codex_protocol::capabilities::SelectedCapabilityRoot;
use codex_utils_absolute_path::AbsolutePathBuf;
Expand Down Expand Up @@ -779,14 +784,16 @@ mod tests {
"composerIcon": "./assets/icon.svg"
}"#,
);
let host_manifest = load_plugin_manifest(&plugin_root).expect("host manifest");
let plugin_root =
AbsolutePathBuf::from_absolute_path_checked(plugin_root).expect("absolute plugin root");
let plugin_root_uri = PathUri::from_abs_path(&plugin_root);
let provider =
ExecutorPluginProvider::new(Arc::new(EnvironmentManager::default_for_tests()));
let selected_root = SelectedCapabilityRoot {
id: "selected-demo".to_string(),
location: CapabilityRootLocation::Environment {
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
path: plugin_root.to_string_lossy().into_owned(),
path: plugin_root_uri.clone(),
},
};

Expand All @@ -795,17 +802,77 @@ mod tests {
.await
.expect("resolve executor plugin")
.expect("plugin descriptor");
let plugin_root =
AbsolutePathBuf::from_absolute_path_checked(plugin_root).expect("absolute plugin root");
let manifest_path = plugin_root_uri
.join(".codex-plugin/plugin.json")
.expect("manifest URI");
let manifest_contents =
fs::read_to_string(plugin_root.join(".codex-plugin/plugin.json")).expect("manifest");
let expected_manifest =
super::parse_plugin_manifest_uri(&plugin_root_uri, &manifest_path, &manifest_contents)
.expect("URI manifest");
let expected_plugin = ResolvedPlugin::from_environment(
"selected-demo".to_string(),
LOCAL_ENVIRONMENT_ID.to_string(),
plugin_root.clone(),
plugin_root.join(".codex-plugin/plugin.json"),
host_manifest,
plugin_root_uri,
manifest_path,
expected_manifest,
)
.expect("valid expected descriptor");

assert_eq!(executor_plugin, expected_plugin);
}

#[test]
fn uri_manifest_resolves_resources_below_foreign_root() {
let plugin_root =
PathUri::parse("file:///C:/plugins/demo-plugin").expect("plugin root URI");
let manifest_path = plugin_root
.join(".codex-plugin/plugin.json")
.expect("manifest URI");
let manifest = super::parse_plugin_manifest_uri(
&plugin_root,
&manifest_path,
r#"{
"name": "demo-plugin",
"skills": "./skills",
"mcpServers": "./.mcp.json",
"apps": "./apps",
"hooks": "./hooks.json",
"interface": {
"displayName": "Demo Plugin",
"composerIcon": "./assets/icon.svg"
}
}"#,
)
.expect("URI manifest");

assert_eq!(
manifest,
GenericPluginManifest {
name: "demo-plugin".to_string(),
version: None,
description: None,
keywords: Vec::new(),
paths: PluginManifestPaths {
skills: vec![plugin_root.join("skills").expect("skills URI")],
mcp_servers: Some(PluginManifestMcpServers::Path(
plugin_root.join(".mcp.json").expect("MCP URI"),
)),
apps: Some(plugin_root.join("apps").expect("apps URI")),
hooks: Some(PluginManifestHooks::Paths(vec![
plugin_root.join("hooks.json").expect("hooks URI"),
])),
},
interface: Some(PluginManifestInterface {
display_name: Some("Demo Plugin".to_string()),
composer_icon: Some(
plugin_root
.join("assets/icon.svg")
.expect("composer icon URI"),
),
..PluginManifestInterface::default()
}),
}
);
}
}
Loading
Loading