chantier: durcissement reprise headless inter-projets + persistance profils Opencode
- Implémentation reprise de session Opencode headless (conversational_recovery) - Persistance des profils IA Opencode dans .ideai/memory avec JSON Schema - Gestion des permissions MCP pour agents externes - Tests QA verts : structured_launch_d3, conversation_log, tickets_missing_carnet, agents, ProfilesSettings
This commit is contained in:
@ -34,7 +34,7 @@ use tokio::task::JoinHandle;
|
||||
use uuid::Uuid;
|
||||
|
||||
use application::{
|
||||
AssignIssueAgentInput, AssignSkillToAgentInput, AssignTicketToSprintInput,
|
||||
AppError, AssignIssueAgentInput, AssignSkillToAgentInput, AssignTicketToSprintInput,
|
||||
AttachLiveAgentInput, AuthenticateSessionInput, ChangeAgentProfileInput, CloseTerminalInput,
|
||||
CreateAgentInput, CreateMemoryInput, CreateSkillInput, CreateSprintInput, DeleteAgentInput,
|
||||
DeleteEmbedderProfileInput, DeleteIssueInput, DeleteMemoryInput, DeleteSkillInput,
|
||||
@ -2983,17 +2983,17 @@ async fn invoke_ticket_read(args: &Value, state: &BackendCore) -> Result<Value,
|
||||
.await
|
||||
.map_err(ErrorDto::from)?
|
||||
.issue;
|
||||
let fallback_carnet = issue.carnet.as_str().to_owned();
|
||||
let carnet = if request.include_carnet.unwrap_or(false) {
|
||||
Some(
|
||||
state
|
||||
.read_issue_carnet
|
||||
.execute(ReadIssueCarnetInput { project, issue_ref })
|
||||
.await
|
||||
.map_err(ErrorDto::from)?
|
||||
.carnet
|
||||
.carnet
|
||||
.as_str()
|
||||
.to_owned(),
|
||||
read_ticket_carnet_body_best_effort(
|
||||
&state.read_issue_carnet,
|
||||
&project,
|
||||
issue_ref,
|
||||
fallback_carnet,
|
||||
)
|
||||
.await
|
||||
.map_err(ErrorDto::from)?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@ -3001,6 +3001,25 @@ async fn invoke_ticket_read(args: &Value, state: &BackendCore) -> Result<Value,
|
||||
serde_json::to_value(TicketDto::from_issue(issue, carnet)).map_err(serialization_error)
|
||||
}
|
||||
|
||||
async fn read_ticket_carnet_body_best_effort(
|
||||
read_carnet: &application::ReadIssueCarnet,
|
||||
project: &Project,
|
||||
issue_ref: domain::IssueRef,
|
||||
fallback_carnet: String,
|
||||
) -> Result<String, AppError> {
|
||||
match read_carnet
|
||||
.execute(ReadIssueCarnetInput {
|
||||
project: project.clone(),
|
||||
issue_ref,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(out) => Ok(out.carnet.carnet.as_str().to_owned()),
|
||||
Err(AppError::NotFound(_)) => Ok(fallback_carnet),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
async fn invoke_ticket_list(args: &Value, state: &BackendCore) -> Result<Value, ErrorDto> {
|
||||
let request = required_request::<TicketListRequestDto>("ticket_list", args)?;
|
||||
let project = resolve_project_readonly(&request.project_id, state).await?;
|
||||
@ -7902,6 +7921,34 @@ mod tests {
|
||||
assert_eq!(read["ref"], ticket_ref);
|
||||
assert!(read["carnet"].is_string());
|
||||
|
||||
let project = state
|
||||
.app
|
||||
.open_project
|
||||
.execute(OpenProjectInput {
|
||||
project_id: ProjectId::from_uuid(Uuid::parse_str(&project_id).unwrap()),
|
||||
})
|
||||
.await
|
||||
.expect("test project is readable")
|
||||
.project;
|
||||
std::fs::remove_file(
|
||||
std::path::PathBuf::from(project.root.as_str())
|
||||
.join(".ideai")
|
||||
.join("tickets")
|
||||
.join(ticket_ref.trim_start_matches('#'))
|
||||
.join("carnet.md"),
|
||||
)
|
||||
.expect("test removes ticket carnet");
|
||||
let (status, read_without_carnet_file) = invoke_request_for_test(
|
||||
Arc::clone(&state),
|
||||
&cookie,
|
||||
"ticket_read",
|
||||
json!({ "request": { "projectId": project_id, "ref": ticket_ref, "includeCarnet": true } }),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert_eq!(read_without_carnet_file["ref"], ticket_ref);
|
||||
assert_eq!(read_without_carnet_file["carnet"], "");
|
||||
|
||||
let (status, list) = invoke_request_for_test(
|
||||
Arc::clone(&state),
|
||||
&cookie,
|
||||
|
||||
Reference in New Issue
Block a user