mirror of
https://github.com/openai/codex.git
synced 2026-11-02 19:43:24 +00:00
codex: accept relative app-server cwd params
This commit is contained in:
@@ -1373,7 +1373,7 @@
|
||||
"cwds": {
|
||||
"description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces and the official curated marketplace are considered.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -3452,13 +3452,9 @@
|
||||
"WindowsSandboxSetupStartParams": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"mode": {
|
||||
|
||||
@@ -9993,7 +9993,7 @@
|
||||
"cwds": {
|
||||
"description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces and the official curated marketplace are considered.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -15206,13 +15206,9 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"mode": {
|
||||
|
||||
@@ -6816,7 +6816,7 @@
|
||||
"cwds": {
|
||||
"description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces and the official curated marketplace are considered.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -13061,13 +13061,9 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"mode": {
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AbsolutePathBuf": {
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"cwds": {
|
||||
"description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces and the official curated marketplace are considered.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AbsolutePathBuf": {
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"WindowsSandboxSetupMode": {
|
||||
"enum": [
|
||||
"elevated",
|
||||
@@ -15,13 +11,9 @@
|
||||
},
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"mode": {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
|
||||
export type PluginListParams = {
|
||||
/**
|
||||
* Optional working directories used to discover repo marketplaces. When omitted,
|
||||
* only home-scoped marketplaces and the official curated marketplace are considered.
|
||||
*/
|
||||
cwds?: Array<AbsolutePathBuf> | null,
|
||||
cwds?: Array<string> | null,
|
||||
/**
|
||||
* When true, reconcile the official curated marketplace against the remote plugin state
|
||||
* before listing marketplaces.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
import type { WindowsSandboxSetupMode } from "./WindowsSandboxSetupMode";
|
||||
|
||||
export type WindowsSandboxSetupStartParams = { mode: WindowsSandboxSetupMode, cwd?: AbsolutePathBuf | null, };
|
||||
export type WindowsSandboxSetupStartParams = { mode: WindowsSandboxSetupMode, cwd?: string | null, };
|
||||
|
||||
@@ -3259,7 +3259,7 @@ pub struct PluginListParams {
|
||||
/// Optional working directories used to discover repo marketplaces. When omitted,
|
||||
/// only home-scoped marketplaces and the official curated marketplace are considered.
|
||||
#[ts(optional = nullable)]
|
||||
pub cwds: Option<Vec<AbsolutePathBuf>>,
|
||||
pub cwds: Option<Vec<PathBuf>>,
|
||||
/// When true, reconcile the official curated marketplace against the remote plugin state
|
||||
/// before listing marketplaces.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
@@ -5456,7 +5456,7 @@ pub enum WindowsSandboxSetupMode {
|
||||
pub struct WindowsSandboxSetupStartParams {
|
||||
pub mode: WindowsSandboxSetupMode,
|
||||
#[ts(optional = nullable)]
|
||||
pub cwd: Option<AbsolutePathBuf>,
|
||||
pub cwd: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
|
||||
@@ -133,18 +133,29 @@ fn read_file_bytes(path: &Path) -> Result<Vec<u8>> {
|
||||
// fixture test is platform-independent.
|
||||
let text = String::from_utf8(bytes)
|
||||
.with_context(|| format!("expected UTF-8 TypeScript in {}", path.display()))?;
|
||||
let text = text.replace("\r\n", "\n").replace('\r', "\n");
|
||||
// Fixture comparisons care about schema content, not whether the generator
|
||||
// re-prepended the standard banner to every TypeScript file.
|
||||
let text = text
|
||||
.strip_prefix(GENERATED_TS_HEADER)
|
||||
.unwrap_or(&text)
|
||||
.to_string();
|
||||
let text = normalize_typescript_fixture_text(text);
|
||||
return Ok(text.into_bytes());
|
||||
}
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn normalize_typescript_fixture_text(text: String) -> String {
|
||||
let text = text.replace("\r\n", "\n").replace('\r', "\n");
|
||||
// Fixture comparisons care about schema content, not whether the generator
|
||||
// re-prepended the standard banner to every TypeScript file.
|
||||
let text = text.strip_prefix(GENERATED_TS_HEADER).unwrap_or(&text);
|
||||
let mut normalized = String::with_capacity(text.len());
|
||||
for line in text.split_inclusive('\n') {
|
||||
if let Some(line) = line.strip_suffix('\n') {
|
||||
normalized.push_str(line.trim_end());
|
||||
normalized.push('\n');
|
||||
} else {
|
||||
normalized.push_str(line.trim_end());
|
||||
}
|
||||
}
|
||||
normalized
|
||||
}
|
||||
|
||||
fn canonicalize_json(value: &Value) -> Value {
|
||||
match value {
|
||||
Value::Array(items) => {
|
||||
@@ -274,12 +285,11 @@ fn collect_typescript_fixture_file<T: TS + 'static + ?Sized>(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let contents = T::export_to_string().context("export TypeScript fixture content")?;
|
||||
let contents = T::export_to_string()
|
||||
.map(normalize_typescript_fixture_text)
|
||||
.context("export TypeScript fixture content")?;
|
||||
let output_path = normalize_relative_fixture_path(&output_path);
|
||||
files.insert(
|
||||
output_path,
|
||||
contents.replace("\r\n", "\n").replace('\r', "\n"),
|
||||
);
|
||||
files.insert(output_path, contents);
|
||||
|
||||
let mut visitor = TypeScriptFixtureCollector {
|
||||
files,
|
||||
@@ -354,4 +364,14 @@ mod tests {
|
||||
]);
|
||||
assert_eq!(canonicalize_json(&value), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_typescript_fixture_text_trims_trailing_whitespace() {
|
||||
let text = format!("{GENERATED_TS_HEADER}type Example = {{ \r\nfield: string, \n}};\t");
|
||||
|
||||
assert_eq!(
|
||||
normalize_typescript_fixture_text(text),
|
||||
"type Example = {\nfield: string,\n};"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ Example with notification opt-out:
|
||||
- `config/mcpServer/reload` — reload MCP server config from disk and queue a refresh for loaded threads (applied on each thread's next active turn); returns `{}`. Use this after editing `config.toml` without restarting the server.
|
||||
- `mcpServerStatus/list` — enumerate configured MCP servers with their tools and auth status, plus resources/resource templates for `full` detail; supports cursor+limit pagination. If `detail` is omitted, the server defaults to `full`.
|
||||
- `mcpServer/resource/read` — read a resource from a thread's configured MCP server by `threadId`, `server`, and `uri`, returning text/blob resource `contents`.
|
||||
- `windowsSandbox/setupStart` — start Windows sandbox setup for the selected mode (`elevated` or `unelevated`); accepts an optional absolute `cwd` to target setup for a specific workspace, returns `{ started: true }` immediately, and later emits `windowsSandbox/setupCompleted`.
|
||||
- `windowsSandbox/setupStart` — start Windows sandbox setup for the selected mode (`elevated` or `unelevated`); accepts an optional `cwd` to target setup for a specific workspace, returns `{ started: true }` immediately, and later emits `windowsSandbox/setupCompleted`.
|
||||
- `feedback/upload` — submit a feedback report (classification + optional reason/logs, conversation_id, and optional `extraLogFiles` attachments array); returns the tracking thread id.
|
||||
- `config/read` — fetch the effective config on disk after resolving config layering.
|
||||
- `externalAgentConfig/detect` — detect migratable external-agent artifacts with `includeHome` and optional `cwds`; each detected item includes `cwd` (`null` for home).
|
||||
|
||||
@@ -6024,6 +6024,7 @@ impl CodexMessageProcessor {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let normalized_cwd = cwd_abs.to_path_buf();
|
||||
let config_layer_stack = match load_config_layers_state(
|
||||
&self.config.codex_home,
|
||||
Some(cwd_abs),
|
||||
@@ -6052,7 +6053,7 @@ impl CodexMessageProcessor {
|
||||
config.features.enabled(Feature::Plugins),
|
||||
);
|
||||
let skills_input = codex_core::skills::SkillsLoadInput::new(
|
||||
cwd.clone(),
|
||||
normalized_cwd,
|
||||
effective_skill_roots,
|
||||
config_layer_stack,
|
||||
config.bundled_skills_enabled(),
|
||||
@@ -6079,7 +6080,27 @@ impl CodexMessageProcessor {
|
||||
cwds,
|
||||
force_remote_sync,
|
||||
} = params;
|
||||
let roots = cwds.unwrap_or_default();
|
||||
let roots = match cwds
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(AbsolutePathBuf::relative_to_current_dir)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
{
|
||||
Ok(roots) => roots,
|
||||
Err(err) => {
|
||||
self.outgoing
|
||||
.send_error(
|
||||
request_id,
|
||||
JSONRPCErrorError {
|
||||
code: INTERNAL_ERROR_CODE,
|
||||
message: format!("invalid plugin cwd: {err}"),
|
||||
data: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
plugins_manager.maybe_start_non_curated_plugin_cache_refresh_for_roots(&roots);
|
||||
|
||||
let mut config = match self.load_latest_config(/*fallback_cwd*/ None).await {
|
||||
@@ -7865,6 +7886,25 @@ impl CodexMessageProcessor {
|
||||
request_id: ConnectionRequestId,
|
||||
params: WindowsSandboxSetupStartParams,
|
||||
) {
|
||||
let command_cwd = match params.cwd {
|
||||
Some(cwd) => match AbsolutePathBuf::relative_to_current_dir(cwd) {
|
||||
Ok(cwd) => cwd,
|
||||
Err(err) => {
|
||||
self.outgoing
|
||||
.send_error(
|
||||
request_id,
|
||||
JSONRPCErrorError {
|
||||
code: INTERNAL_ERROR_CODE,
|
||||
message: format!("invalid windows sandbox setup cwd: {err}"),
|
||||
data: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => self.config.cwd.clone(),
|
||||
};
|
||||
self.outgoing
|
||||
.send_response(
|
||||
request_id.clone(),
|
||||
@@ -7878,7 +7918,6 @@ impl CodexMessageProcessor {
|
||||
};
|
||||
let config = Arc::clone(&self.config);
|
||||
let cloud_requirements = self.current_cloud_requirements();
|
||||
let command_cwd = params.cwd.unwrap_or_else(|| config.cwd.clone());
|
||||
let cli_overrides = self.current_cli_overrides();
|
||||
let runtime_feature_enablement = self.current_runtime_feature_enablement();
|
||||
let outgoing = Arc::clone(&self.outgoing);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -67,7 +68,7 @@ async fn plugin_list_skips_invalid_marketplace_file_and_reports_error() -> Resul
|
||||
|
||||
let request_id = mcp
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
|
||||
cwds: Some(vec![repo_root.path().to_path_buf()]),
|
||||
force_remote_sync: false,
|
||||
})
|
||||
.await?;
|
||||
@@ -102,28 +103,58 @@ async fn plugin_list_skips_invalid_marketplace_file_and_reports_error() -> Resul
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_list_rejects_relative_cwds() -> Result<()> {
|
||||
async fn plugin_list_resolves_relative_cwds() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
write_plugins_enabled_config(codex_home.path())?;
|
||||
let repo_root = codex_home.path().join("relative-root");
|
||||
std::fs::create_dir_all(repo_root.join(".git"))?;
|
||||
std::fs::create_dir_all(repo_root.join(".agents/plugins"))?;
|
||||
std::fs::create_dir_all(repo_root.join("plugins/relative-plugin/.codex-plugin"))?;
|
||||
std::fs::write(
|
||||
repo_root.join(".agents/plugins/marketplace.json"),
|
||||
r#"{
|
||||
"name": "relative-marketplace",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "relative-plugin",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/relative-plugin"
|
||||
}
|
||||
}
|
||||
]
|
||||
}"#,
|
||||
)?;
|
||||
std::fs::write(
|
||||
repo_root.join("plugins/relative-plugin/.codex-plugin/plugin.json"),
|
||||
r#"{"name":"relative-plugin"}"#,
|
||||
)?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_raw_request(
|
||||
"plugin/list",
|
||||
Some(serde_json::json!({
|
||||
"cwds": ["relative-root"],
|
||||
})),
|
||||
)
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: Some(vec![PathBuf::from("relative-root")]),
|
||||
force_remote_sync: false,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let err = timeout(
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let response: PluginListResponse = to_response(response)?;
|
||||
|
||||
assert_eq!(err.error.code, -32600);
|
||||
assert!(err.error.message.contains("Invalid request"));
|
||||
assert!(
|
||||
response
|
||||
.marketplaces
|
||||
.iter()
|
||||
.any(|marketplace| marketplace.name == "relative-marketplace"),
|
||||
"relative cwd marketplace should be discovered: {:?}",
|
||||
response.marketplaces
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -194,8 +225,8 @@ async fn plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_
|
||||
let request_id = mcp
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: Some(vec![
|
||||
AbsolutePathBuf::try_from(valid_repo_root.path())?,
|
||||
AbsolutePathBuf::try_from(invalid_repo_root.path())?,
|
||||
valid_repo_root.path().to_path_buf(),
|
||||
invalid_repo_root.path().to_path_buf(),
|
||||
]),
|
||||
force_remote_sync: false,
|
||||
})
|
||||
@@ -350,7 +381,7 @@ enabled = false
|
||||
|
||||
let request_id = mcp
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
|
||||
cwds: Some(vec![repo_root.path().to_path_buf()]),
|
||||
force_remote_sync: false,
|
||||
})
|
||||
.await?;
|
||||
@@ -503,8 +534,8 @@ enabled = false
|
||||
let request_id = mcp
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: Some(vec![
|
||||
AbsolutePathBuf::try_from(workspace_enabled.path())?,
|
||||
AbsolutePathBuf::try_from(workspace_default.path())?,
|
||||
workspace_enabled.path().to_path_buf(),
|
||||
workspace_default.path().to_path_buf(),
|
||||
]),
|
||||
force_remote_sync: false,
|
||||
})
|
||||
@@ -589,7 +620,7 @@ async fn plugin_list_returns_plugin_interface_with_absolute_asset_paths() -> Res
|
||||
|
||||
let request_id = mcp
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
|
||||
cwds: Some(vec![repo_root.path().to_path_buf()]),
|
||||
force_remote_sync: false,
|
||||
})
|
||||
.await?;
|
||||
@@ -702,7 +733,7 @@ async fn plugin_list_accepts_legacy_string_default_prompt() -> Result<()> {
|
||||
|
||||
let request_id = mcp
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
|
||||
cwds: Some(vec![repo_root.path().to_path_buf()]),
|
||||
force_remote_sync: false,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
@@ -26,6 +27,14 @@ fn write_skill(root: &TempDir, name: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_skill_at_root(root: &std::path::Path, name: &str) -> Result<()> {
|
||||
let skill_dir = root.join(name);
|
||||
std::fs::create_dir_all(&skill_dir)?;
|
||||
let content = format!("---\nname: {name}\ndescription: {name} description\n---\n\n# Body\n");
|
||||
std::fs::write(skill_dir.join("SKILL.md"), content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn skills_list_includes_skills_from_per_cwd_extra_user_roots() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
@@ -64,6 +73,41 @@ async fn skills_list_includes_skills_from_per_cwd_extra_user_roots() -> Result<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn skills_list_resolves_relative_cwd_for_repo_skills() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let repo_root = codex_home.path().join("relative-root");
|
||||
std::fs::create_dir_all(repo_root.join(".git"))?;
|
||||
write_skill_at_root(&repo_root.join(".agents/skills"), "repo-skill")?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_skills_list_request(SkillsListParams {
|
||||
cwds: vec![PathBuf::from("relative-root")],
|
||||
force_reload: true,
|
||||
per_cwd_extra_user_roots: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let SkillsListResponse { data } = to_response(response)?;
|
||||
assert_eq!(data.len(), 1);
|
||||
assert_eq!(data[0].cwd, PathBuf::from("relative-root"));
|
||||
assert!(
|
||||
data[0]
|
||||
.skills
|
||||
.iter()
|
||||
.any(|skill| skill.name == "repo-skill")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn skills_list_rejects_relative_extra_user_roots() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
@@ -64,8 +64,9 @@ async fn windows_sandbox_setup_start_emits_completion_notification() -> Result<(
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn windows_sandbox_setup_start_rejects_relative_cwd() -> Result<()> {
|
||||
async fn windows_sandbox_setup_start_accepts_relative_cwd() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
std::fs::create_dir_all(codex_home.path().join("relative-root"))?;
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
@@ -79,13 +80,13 @@ async fn windows_sandbox_setup_start_rejects_relative_cwd() -> Result<()> {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let err = timeout(
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let start_payload: WindowsSandboxSetupStartResponse = to_response(response)?;
|
||||
|
||||
assert_eq!(err.error.code, -32600);
|
||||
assert!(err.error.message.contains("Invalid request"));
|
||||
assert!(start_payload.started);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user