fix(#108): restreint Code Mode Codex au namespace MCP IdeA et documente les tools au démarrage

Codex Code Mode pouvait appeler n'importe quel outil MCP directement,
contournant la médiation d'approbation. On force
[features.code_mode].direct_only_tool_namespaces = ["mcp__idea"] sur
chaque surface qui écrit le config.toml Codex (permission projector,
lifecycle, migration run-dir, assistant de ticket), et on ajoute
initialize.instructions côté serveur MCP pour orienter Codex vers le
bon outil idea_* dès la connexion, sans dépendre de la recherche
sémantique différée.

QA : domain 283/0, application 126/0 + agent_lifecycle 73/0 +
change_agent_profile 19/0 + ticket_assistant 5/0, infrastructure 339/0
dont mcp_server 37/0, backend 68/0 (7 ignored). Les échecs web-server
observés sur cargo test --workspace (Too many open files, cookies)
sont une contamination de ressources inter-tests ; les deux tests
concernés repassent isolément.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 22:51:38 +02:00
parent af6b76935c
commit d0b65a92bd
9 changed files with 228 additions and 32 deletions

View File

@ -5,7 +5,10 @@ use std::sync::Arc;
use application::McpRuntime;
use async_trait::async_trait;
use domain::ports::{SecretStore, SessionPlan};
use domain::profile::{McpConfigStrategy, OpenCodeProviderConfig, StructuredAdapter};
use domain::profile::{
McpConfigStrategy, OpenCodeProviderConfig, StructuredAdapter, CODEX_CODE_MODE_FEATURES_TABLE,
CODEX_CODE_MODE_FEATURES_TOML,
};
use domain::{
AgentProfile, AgentRuntime, AssistantContextError, AssistantContextProvider,
ContextInjectionPlan, EffectivePermissions, FileSystem, FsError, Issue, IssueRef, MarkdownDoc,
@ -522,6 +525,11 @@ fn codex_config_toml(
text = set_top_level_toml_value(&text, "model", model);
}
text = replace_toml_table_block(&text, "mcp_servers.idea", mcp_declaration.trim_end());
text = replace_toml_table_block(
&text,
CODEX_CODE_MODE_FEATURES_TABLE,
CODEX_CODE_MODE_FEATURES_TOML.trim_end(),
);
text = ensure_codex_project_trust(&text, run_dir);
text = ensure_codex_project_trust(&text, project_root);
if !text.ends_with('\n') {
@ -645,6 +653,60 @@ fn parent_dir(base: &ProjectPath, rel: &str) -> String {
}
}
#[cfg(test)]
mod codex_config_toml_tests {
use domain::profile::McpTransport;
use super::*;
#[test]
fn assistant_codex_config_replaces_code_mode_and_preserves_user_content() {
let declaration = McpServerWiring::new(
"idea".to_owned(),
vec!["mcp-server".to_owned()],
McpTransport::Stdio,
)
.to_config_toml();
let existing = "user_key = \"keep\"\n\n[features.code_mode]\ndirect_only_tool_namespaces = [\"old\"]\n\n[features.preview]\nenabled = true\n\n[user.table]\nvalue = 1\n";
let rendered = codex_config_toml(
Some(existing),
&declaration,
"/run/assistant",
"/proj",
None,
);
assert!(
rendered.contains("user_key = \"keep\""),
"unmanaged top-level key preserved: {rendered}"
);
assert!(
rendered.contains("[user.table]\nvalue = 1"),
"unmanaged table preserved: {rendered}"
);
assert!(
rendered.contains("[features.preview]\nenabled = true"),
"unmanaged sibling features table preserved: {rendered}"
);
assert!(
rendered
.contains("[features.code_mode]\ndirect_only_tool_namespaces = [\"mcp__idea\"]"),
"assistant Codex config should inject direct-only IdeA MCP namespace: {rendered}"
);
assert_eq!(
rendered.matches("[features.code_mode]").count(),
1,
"assistant Codex config should replace, not duplicate, code mode table: {rendered}"
);
assert!(
rendered.contains("[projects.\"/run/assistant\"]")
&& rendered.contains("[projects.\"/proj\"]"),
"trust entries should still be generated: {rendered}"
);
}
}
#[cfg(test)]
mod opencode_provider_config_json_tests {
use domain::ports::SecretRef;