fix(backend): identité requester explicite + policy des tools OpenAI-compatible (#62)

AgentSessionFactory::start propage désormais l'identité du requester aux
sessions structurées ; OpenTicketAssistant et LaunchAgent la portent
correctement de bout en bout. ToolPolicyRegistry est branché sur
AppOpenAiToolInvoker pour combler le trou de parité : TicketToolProvider
n'appliquait pas la policy des tools sur le chemin OpenAI-compatible,
contrairement au chemin structuré natif.

QA vert (échecs de bind loopback écartés comme non-régression préexistante,
tracés en #80).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 22:35:17 +02:00
parent a7abd331b8
commit b4a34b40e5
15 changed files with 555 additions and 19 deletions

View File

@ -1853,7 +1853,15 @@ impl LaunchAgent {
// Relaie le plan de sandbox OS (lot LP4-4) à la fabrique : `spec.sandbox`,
// déjà compilé (pur, domaine) en step 5d. `None` ⇒ exécution native inchangée.
let session = factory
.start(profile, prepared, run_dir, session_plan, env, sandbox)
.start(
profile,
prepared,
run_dir,
session_plan,
Some(&agent.id.to_string()),
env,
sandbox,
)
.await
.map_err(|e| AppError::Process(e.to_string()))?;

View File

@ -143,6 +143,7 @@ impl OpenTicketAssistant {
&prepared,
&environment.cwd,
&SessionPlan::None,
Some(&requester),
&environment.env,
None,
)

View File

@ -627,6 +627,7 @@ impl AgentSessionFactory for FakeStructuredFactory {
_ctx: &PreparedContext,
_cwd: &ProjectPath,
_session: &SessionPlan,
_requester: Option<&str>,
_env: &[(String, String)],
_sandbox: Option<&domain::sandbox::SandboxPlan>,
) -> Result<Arc<dyn AgentSession>, AgentSessionError> {

View File

@ -1488,6 +1488,7 @@ impl AgentSessionFactory for CompletionFactory {
ctx: &PreparedContext,
_cwd: &ProjectPath,
_session: &SessionPlan,
_requester: Option<&str>,
_env: &[(String, String)],
_sandbox: Option<&domain::sandbox::SandboxPlan>,
) -> Result<Arc<dyn AgentSession>, AgentSessionError> {
@ -4096,6 +4097,7 @@ impl AgentSessionFactory for CountingFactory {
_ctx: &PreparedContext,
_cwd: &ProjectPath,
_session: &SessionPlan,
_requester: Option<&str>,
_env: &[(String, String)],
_sandbox: Option<&domain::sandbox::SandboxPlan>,
) -> Result<Arc<dyn AgentSession>, AgentSessionError> {

View File

@ -528,6 +528,7 @@ impl AgentSessionFactory for FakeFactory {
_ctx: &PreparedContext,
_cwd: &ProjectPath,
session: &SessionPlan,
_requester: Option<&str>,
_env: &[(String, String)],
_sandbox: Option<&domain::sandbox::SandboxPlan>,
) -> Result<Arc<dyn AgentSession>, AgentSessionError> {
@ -1126,7 +1127,7 @@ async fn swap_structured_live_session_shuts_down_then_relaunches() {
let cwd = ProjectPath::new(ROOT).unwrap();
let session = f
.factory
.start(&profile, &ctx, &cwd, &SessionPlan::None, &[], None)
.start(&profile, &ctx, &cwd, &SessionPlan::None, None, &[], None)
.await
.expect("seed structured session");
f.structured.insert(session, agent.id, host);

View File

@ -251,6 +251,7 @@ struct FakeFactory {
PreparedContext,
ProjectPath,
SessionPlan,
Option<String>,
Vec<(String, String)>,
Option<domain::SandboxPlan>,
)>,
@ -270,6 +271,7 @@ impl AgentSessionFactory for FakeFactory {
ctx: &PreparedContext,
cwd: &ProjectPath,
session: &SessionPlan,
requester: Option<&str>,
env: &[(String, String)],
sandbox: Option<&domain::SandboxPlan>,
) -> Result<Arc<dyn AgentSession>, AgentSessionError> {
@ -277,6 +279,7 @@ impl AgentSessionFactory for FakeFactory {
ctx.clone(),
cwd.clone(),
session.clone(),
requester.map(str::to_owned),
env.to_vec(),
sandbox.cloned(),
));
@ -381,14 +384,15 @@ async fn open_then_close_ticket_assistant_sets_policy_injects_context_and_emits_
let starts = factory.starts.lock().unwrap();
assert_eq!(starts[0].1.as_str(), "/tmp/app-data/assistant/tickets/1/7");
assert!(matches!(starts[0].2, SessionPlan::None));
assert_eq!(starts[0].3.as_deref(), Some(output.requester.as_str()));
assert_eq!(
starts[0].3,
starts[0].4,
vec![(
"CODEX_HOME".to_owned(),
"/tmp/app-data/assistant/tickets/1/7/.codex".to_owned()
)]
);
assert!(starts[0].4.is_none());
assert!(starts[0].5.is_none());
drop(starts);
close