fix(permissions): tools MCP inter-projets exposés (idea_project_*) + contactable agent/template persisté — update_agent_contactable (application/web-server/app-tauri), defaultContactable TemplateEditor save/reopen, groupes MCP 'IdeA inter-projets' avec fallback, permissions agent Contactable — #267 (QA verte)
This commit is contained in:
@ -29,8 +29,8 @@ use application::{
|
||||
ResolveAgentSystemPermissionsInput, ResolveMemoryLinksInput, RevokeDeviceInput,
|
||||
RotateConversationLogInput, SaveChatDraftInput, SetActiveLayoutInput, SlashCommandEffect,
|
||||
SnapshotRunningAgentsInput, StopLiveAgentInput, SyncAgentWithTemplateInput,
|
||||
UnassignSkillFromAgentInput, UpdateAgentContextInput, UpdateAgentEffortInput,
|
||||
UpdateAgentMcpToolPermissionsInput, UpdateAgentPermissionsInput,
|
||||
UnassignSkillFromAgentInput, UpdateAgentContactableInput, UpdateAgentContextInput,
|
||||
UpdateAgentEffortInput, UpdateAgentMcpToolPermissionsInput, UpdateAgentPermissionsInput,
|
||||
UpdateAgentSystemPermissionsInput, UpdateMemoryInput, UpdateProjectContextInput,
|
||||
UpdateProjectMcpToolPermissionsInput, UpdateProjectPermissionsInput,
|
||||
UpdateProjectSystemPermissionsInput, UpdateSkillInput,
|
||||
@ -78,7 +78,7 @@ use crate::dto::{
|
||||
SlashCommandEffectDto, SlashCommandListDto, StopLiveAgentRequestDto, StopLiveAgentResponseDto,
|
||||
SyncAgentWithTemplateRequestDto, SyncResultDto, TemplateDto, TemplateListDto,
|
||||
TerminalClosedDto, TerminalSessionDto, TurnPageDto, UnassignSkillRequestDto,
|
||||
UpdateAgentContextRequestDto, UpdateAgentEffortRequestDto,
|
||||
UpdateAgentContactableRequestDto, UpdateAgentContextRequestDto, UpdateAgentEffortRequestDto,
|
||||
UpdateAgentMcpToolPermissionsRequestDto, UpdateAgentPermissionsRequestDto,
|
||||
UpdateAgentSystemPermissionsRequestDto, UpdateMemoryRequestDto, UpdateProjectContextRequestDto,
|
||||
UpdateProjectMcpToolPermissionsRequestDto, UpdateProjectPermissionsRequestDto,
|
||||
@ -777,6 +777,29 @@ pub async fn update_agent_effort(
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `update_agent_contactable` — enable or disable cross-project contact for one agent.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] on invalid ids or store failure.
|
||||
#[tauri::command]
|
||||
pub async fn update_agent_contactable(
|
||||
request: UpdateAgentContactableRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<AgentDto, ErrorDto> {
|
||||
let project = resolve_project(&request.project_id, &state).await?;
|
||||
let agent_id = parse_agent_id(&request.agent_id)?;
|
||||
state
|
||||
.update_agent_contactable
|
||||
.execute(UpdateAgentContactableInput {
|
||||
project,
|
||||
agent_id,
|
||||
contactable: request.contactable,
|
||||
})
|
||||
.await
|
||||
.map(|out| AgentDto::from_agent(out.agent))
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `resolve_agent_permissions` — resolve project defaults plus agent override.
|
||||
///
|
||||
/// # Errors
|
||||
|
||||
@ -261,6 +261,7 @@ pub fn run() {
|
||||
commands::update_project_permissions,
|
||||
commands::update_agent_permissions,
|
||||
commands::update_agent_effort,
|
||||
commands::update_agent_contactable,
|
||||
commands::resolve_agent_permissions,
|
||||
commands::get_project_system_permissions,
|
||||
commands::update_project_system_permissions,
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
use app_tauri_lib::dto::{
|
||||
parse_agent_id, AgentDto, AgentListDto, CellKind, ConversationDetailsDto,
|
||||
CreateAgentRequestDto, InspectConversationRequestDto, LaunchAgentRequestDto, LiveAgentListDto,
|
||||
TerminalSessionDto, UpdateAgentContextRequestDto, UpdateAgentEffortRequestDto,
|
||||
TerminalSessionDto, UpdateAgentContactableRequestDto, UpdateAgentContextRequestDto,
|
||||
UpdateAgentEffortRequestDto,
|
||||
};
|
||||
use application::AppError;
|
||||
use application::{
|
||||
@ -40,7 +41,7 @@ fn make_agent(agent_uuid: u128, profile_uuid: u128) -> Agent {
|
||||
|
||||
#[test]
|
||||
fn agent_dto_serialises_camelcase() {
|
||||
let agent = make_agent(1, 2);
|
||||
let agent = make_agent(1, 2).with_contactable(true);
|
||||
let dto = AgentDto::from_agent(agent.clone());
|
||||
let v = serde_json::to_value(&dto).unwrap();
|
||||
|
||||
@ -52,6 +53,7 @@ fn agent_dto_serialises_camelcase() {
|
||||
ProfileId::from_uuid(Uuid::from_u128(2)).to_string()
|
||||
);
|
||||
assert_eq!(v["synchronized"], false);
|
||||
assert_eq!(v["contactable"], true);
|
||||
// origin: tagged `{ "type": "scratch" }`
|
||||
assert_eq!(v["origin"]["type"], "scratch");
|
||||
assert_eq!(v["capabilities"], json!([]));
|
||||
@ -173,6 +175,19 @@ fn update_agent_effort_request_deserialises_and_null_clears() {
|
||||
assert_eq!(clear.effort, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_agent_contactable_request_deserialises_camelcase() {
|
||||
let raw = json!({
|
||||
"projectId": Uuid::from_u128(1).to_string(),
|
||||
"agentId": Uuid::from_u128(2).to_string(),
|
||||
"contactable": true
|
||||
});
|
||||
let dto: UpdateAgentContactableRequestDto = serde_json::from_value(raw).unwrap();
|
||||
assert!(dto.contactable);
|
||||
assert_eq!(dto.project_id, Uuid::from_u128(1).to_string());
|
||||
assert_eq!(dto.agent_id, Uuid::from_u128(2).to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn launch_agent_request_deserialises_camelcase() {
|
||||
let project_id = Uuid::from_u128(1).to_string();
|
||||
|
||||
@ -522,6 +522,72 @@ pub struct UpdateAgentEffortOutput {
|
||||
pub agent: Agent,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// UpdateAgentContactable
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Input for [`UpdateAgentContactable::execute`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UpdateAgentContactableInput {
|
||||
/// The owning project.
|
||||
pub project: Project,
|
||||
/// The agent whose cross-project contactability changes.
|
||||
pub agent_id: AgentId,
|
||||
/// Whether agents from other IdeA projects may contact this agent.
|
||||
pub contactable: bool,
|
||||
}
|
||||
|
||||
/// Output of [`UpdateAgentContactable::execute`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UpdateAgentContactableOutput {
|
||||
/// The updated agent.
|
||||
pub agent: Agent,
|
||||
}
|
||||
|
||||
/// Updates an agent's cross-project contactability in the project manifest.
|
||||
pub struct UpdateAgentContactable {
|
||||
contexts: Arc<dyn AgentContextStore>,
|
||||
}
|
||||
|
||||
impl UpdateAgentContactable {
|
||||
/// Builds the use case.
|
||||
#[must_use]
|
||||
pub fn new(contexts: Arc<dyn AgentContextStore>) -> Self {
|
||||
Self { contexts }
|
||||
}
|
||||
|
||||
/// Executes the update.
|
||||
///
|
||||
/// # Errors
|
||||
/// - [`AppError::NotFound`] if the agent is unknown to the project,
|
||||
/// - [`AppError::Invalid`] if the resulting manifest is invalid,
|
||||
/// - [`AppError::Store`] on persistence failure.
|
||||
pub async fn execute(
|
||||
&self,
|
||||
input: UpdateAgentContactableInput,
|
||||
) -> Result<UpdateAgentContactableOutput, AppError> {
|
||||
let mut manifest = self.contexts.load_manifest(&input.project).await?;
|
||||
let entry = manifest
|
||||
.entries
|
||||
.iter_mut()
|
||||
.find(|entry| entry.agent_id == input.agent_id)
|
||||
.ok_or_else(|| AppError::NotFound(format!("agent {}", input.agent_id)))?;
|
||||
|
||||
let agent = entry
|
||||
.to_agent()
|
||||
.map_err(|err| AppError::Invalid(err.to_string()))?
|
||||
.with_contactable(input.contactable);
|
||||
*entry = ManifestEntry::from_agent(&agent);
|
||||
|
||||
let manifest = AgentManifest::new(manifest.version, manifest.entries)
|
||||
.map_err(|err| AppError::Invalid(err.to_string()))?;
|
||||
self.contexts
|
||||
.save_manifest(&input.project, &manifest)
|
||||
.await?;
|
||||
Ok(UpdateAgentContactableOutput { agent })
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets or clears an agent's per-agent effort override in the manifest.
|
||||
///
|
||||
/// The change applies at the agent's next launch; live sessions are not mutated.
|
||||
|
||||
@ -37,7 +37,8 @@ pub use lifecycle::{
|
||||
LaunchAgentOutput, ListAgents, ListAgentsInput, ListAgentsOutput, ListedAgentCapabilities,
|
||||
LiveStateLeanProvider, ManagedTempExposureRequest, McpRuntime, PermissionProjectorRegistry,
|
||||
ProviderSessionProvider, ReadAgentContext, ReadAgentContextInput, ReadAgentContextOutput,
|
||||
StructuredRoutingMode, StructuredSessionDescriptor, UpdateAgentContext,
|
||||
StructuredRoutingMode, StructuredSessionDescriptor, UpdateAgentContactable,
|
||||
UpdateAgentContactableInput, UpdateAgentContactableOutput, UpdateAgentContext,
|
||||
UpdateAgentContextInput, UpdateAgentEffort, UpdateAgentEffortInput, UpdateAgentEffortOutput,
|
||||
AGENT_MEMORY_RECALL_BUDGET, DEFAULT_OPENCODE_MCP_TIMEOUT_MS, LIVE_STATE_INJECT_MAX,
|
||||
};
|
||||
|
||||
@ -66,8 +66,9 @@ pub use agent::{
|
||||
ReferenceProfiles, ReferenceProfilesOutput, ResumableAgent, SaveOpenCodeProviderProfile,
|
||||
SaveOpenCodeProviderProfileInput, SaveOpenCodeProviderProfileOutput, SaveProfile,
|
||||
SaveProfileInput, SaveProfileOutput, SessionLimitService, StructuredRoutingMode,
|
||||
StructuredSessionDescriptor, TurnOutcome, UpdateAgentContext, UpdateAgentContextInput,
|
||||
UpdateAgentEffort, UpdateAgentEffortInput, UpdateAgentEffortOutput, AGENT_MEMORY_RECALL_BUDGET,
|
||||
StructuredSessionDescriptor, TurnOutcome, UpdateAgentContactable, UpdateAgentContactableInput,
|
||||
UpdateAgentContactableOutput, UpdateAgentContext, UpdateAgentContextInput, UpdateAgentEffort,
|
||||
UpdateAgentEffortInput, UpdateAgentEffortOutput, AGENT_MEMORY_RECALL_BUDGET,
|
||||
CODEX_SUBMIT_DELAY_MS, LIVE_STATE_INJECT_MAX, RESUME_PROMPT,
|
||||
};
|
||||
pub use background::{
|
||||
|
||||
@ -57,8 +57,8 @@ use application::{
|
||||
DeleteAgent, DeleteAgentInput, LaunchAgent, LaunchAgentInput, ListAgents, ListAgentsInput,
|
||||
LiveSessions, ManagedTempExposureRequest, PermissionProjectorRegistry, ReadAgentContext,
|
||||
ReadAgentContextInput, ReleaseManagedTemp, ResolveManagedTemp, StructuredRoutingMode,
|
||||
StructuredSessions, TerminalSessions, UpdateAgentContext, UpdateAgentContextInput,
|
||||
UpdateAgentEffort, UpdateAgentEffortInput,
|
||||
StructuredSessions, TerminalSessions, UpdateAgentContactable, UpdateAgentContactableInput,
|
||||
UpdateAgentContext, UpdateAgentContextInput, UpdateAgentEffort, UpdateAgentEffortInput,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -1163,6 +1163,53 @@ async fn update_agent_effort_not_found_for_unknown_agent_id() {
|
||||
assert_eq!(err.code(), "NOT_FOUND", "got {err:?}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_agent_contactable_persists_manifest_and_can_be_cleared() {
|
||||
let a = scratch_agent(aid(1), "Backend", "agents/backend.md", pid(9));
|
||||
let contexts = FakeContexts::with_agent(&a, "ctx");
|
||||
let update = UpdateAgentContactable::new(Arc::new(contexts.clone()));
|
||||
|
||||
let enabled = update
|
||||
.execute(UpdateAgentContactableInput {
|
||||
project: project(),
|
||||
agent_id: a.id,
|
||||
contactable: true,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(enabled.agent.contactable);
|
||||
assert!(contexts.manifest().entries[0].contactable);
|
||||
|
||||
let disabled = update
|
||||
.execute(UpdateAgentContactableInput {
|
||||
project: project(),
|
||||
agent_id: a.id,
|
||||
contactable: false,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!disabled.agent.contactable);
|
||||
assert!(!contexts.manifest().entries[0].contactable);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_agent_contactable_not_found_for_unknown_agent_id() {
|
||||
let a = scratch_agent(aid(1), "Backend", "agents/backend.md", pid(9));
|
||||
let contexts = FakeContexts::with_agent(&a, "ctx");
|
||||
let update = UpdateAgentContactable::new(Arc::new(contexts));
|
||||
|
||||
let err = update
|
||||
.execute(UpdateAgentContactableInput {
|
||||
project: project(),
|
||||
agent_id: aid(404),
|
||||
contactable: true,
|
||||
})
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(err.code(), "NOT_FOUND", "got {err:?}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_removes_entry_then_unknown_is_not_found() {
|
||||
let a = scratch_agent(aid(1), "Backend", "agents/backend.md", pid(9));
|
||||
|
||||
@ -3067,6 +3067,18 @@ pub struct UpdateAgentEffortRequestDto {
|
||||
pub effort: Option<EffortSelection>,
|
||||
}
|
||||
|
||||
/// Request DTO for `update_agent_contactable`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateAgentContactableRequestDto {
|
||||
/// Id of the owning project.
|
||||
pub project_id: String,
|
||||
/// Id of the agent whose cross-project contactability changes.
|
||||
pub agent_id: String,
|
||||
/// Whether agents from other projects may contact this agent.
|
||||
pub contactable: bool,
|
||||
}
|
||||
|
||||
/// Response DTO for `change_agent_profile`: the mutated agent plus the freshly
|
||||
/// relaunched session when a live session was hot-swapped (absent otherwise).
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
|
||||
@ -54,11 +54,11 @@ use application::{
|
||||
SpawnBackgroundCommand, StopLiveAgent, StructuredRoutingMode, StructuredSessions,
|
||||
SuggestedThisSession, SyncAgentWithTemplate, TerminalSessions, TouchDevice,
|
||||
UnassignSkillFromAgent, UnassignTicketFromSprint, UninstallPlugin, UnlinkIssues,
|
||||
UpdateAgentContext, UpdateAgentEffort, UpdateAgentMcpToolPermissions, UpdateAgentPermissions,
|
||||
UpdateAgentSystemPermissions, UpdateIssue, UpdateIssueCarnet, UpdateLiveState, UpdateMemory,
|
||||
UpdateProjectContext, UpdateProjectMcpToolPermissions, UpdateProjectPermissions,
|
||||
UpdateProjectSystemPermissions, UpdateSkill, UpdateTemplate, WakeSessionProvider, WriteMemory,
|
||||
WriteToTerminal, AGENT_MEMORY_RECALL_BUDGET,
|
||||
UpdateAgentContactable, UpdateAgentContext, UpdateAgentEffort, UpdateAgentMcpToolPermissions,
|
||||
UpdateAgentPermissions, UpdateAgentSystemPermissions, UpdateIssue, UpdateIssueCarnet,
|
||||
UpdateLiveState, UpdateMemory, UpdateProjectContext, UpdateProjectMcpToolPermissions,
|
||||
UpdateProjectPermissions, UpdateProjectSystemPermissions, UpdateSkill, UpdateTemplate,
|
||||
WakeSessionProvider, WriteMemory, WriteToTerminal, AGENT_MEMORY_RECALL_BUDGET,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use domain::ports::{
|
||||
@ -1287,6 +1287,8 @@ pub struct BackendCore {
|
||||
pub update_agent_permissions: Arc<UpdateAgentPermissions>,
|
||||
/// Update one agent effort override.
|
||||
pub update_agent_effort: Arc<UpdateAgentEffort>,
|
||||
/// Update one agent's cross-project contactability.
|
||||
pub update_agent_contactable: Arc<UpdateAgentContactable>,
|
||||
/// Resolve effective permissions for one agent.
|
||||
pub resolve_agent_permissions: Arc<ResolveAgentPermissions>,
|
||||
/// Read the project system permission document.
|
||||
@ -2454,6 +2456,8 @@ impl BackendCore {
|
||||
&permission_store_port,
|
||||
)));
|
||||
let update_agent_effort = Arc::new(UpdateAgentEffort::new(Arc::clone(&contexts_port)));
|
||||
let update_agent_contactable =
|
||||
Arc::new(UpdateAgentContactable::new(Arc::clone(&contexts_port)));
|
||||
let resolve_agent_permissions = Arc::new(ResolveAgentPermissions::new(Arc::clone(
|
||||
&permission_store_port,
|
||||
)));
|
||||
@ -3296,6 +3300,7 @@ impl BackendCore {
|
||||
update_project_permissions,
|
||||
update_agent_permissions,
|
||||
update_agent_effort,
|
||||
update_agent_contactable,
|
||||
resolve_agent_permissions,
|
||||
get_project_system_permissions,
|
||||
update_project_system_permissions,
|
||||
|
||||
@ -58,11 +58,12 @@ use application::{
|
||||
ResolveAgentSystemPermissionsInput, ResolveMemoryLinksInput, RevokeDeviceInput,
|
||||
RotateConversationLogInput, SetActiveLayoutInput, StopLiveAgentInput,
|
||||
SyncAgentWithTemplateInput, TouchDeviceInput, UnassignSkillFromAgentInput,
|
||||
UnassignTicketFromSprintInput, UnlinkIssuesInput, UpdateAgentContextInput,
|
||||
UpdateAgentEffortInput, UpdateAgentMcpToolPermissionsInput, UpdateAgentPermissionsInput,
|
||||
UpdateAgentSystemPermissionsInput, UpdateIssueCarnetInput, UpdateMemoryInput,
|
||||
UpdateProjectContextInput, UpdateProjectMcpToolPermissionsInput, UpdateProjectPermissionsInput,
|
||||
UpdateProjectSystemPermissionsInput, UpdateSkillInput, WriteToTerminalInput,
|
||||
UnassignTicketFromSprintInput, UnlinkIssuesInput, UpdateAgentContactableInput,
|
||||
UpdateAgentContextInput, UpdateAgentEffortInput, UpdateAgentMcpToolPermissionsInput,
|
||||
UpdateAgentPermissionsInput, UpdateAgentSystemPermissionsInput, UpdateIssueCarnetInput,
|
||||
UpdateMemoryInput, UpdateProjectContextInput, UpdateProjectMcpToolPermissionsInput,
|
||||
UpdateProjectPermissionsInput, UpdateProjectSystemPermissionsInput, UpdateSkillInput,
|
||||
WriteToTerminalInput,
|
||||
};
|
||||
use domain::ports::{
|
||||
PtyHandle, ReplyEvent, ReplyProgress, ReplyProgressKind, ReplyProgressSource,
|
||||
@ -108,10 +109,10 @@ use backend::dto::{
|
||||
TicketDeleteRequestDto, TicketDto, TicketLinkCommandRequestDto, TicketListPageInput,
|
||||
TicketListRequestDto, TicketReadRequestDto, TicketSprintAssignRequestDto,
|
||||
TicketSprintUnassignRequestDto, TicketUnlinkCommandRequestDto, TicketUpdateCarnetRequestDto,
|
||||
TicketUpdateRequestDto, TurnPageDto, UnassignSkillRequestDto, UpdateAgentContextRequestDto,
|
||||
UpdateAgentEffortRequestDto, UpdateAgentMcpToolPermissionsRequestDto,
|
||||
UpdateAgentPermissionsRequestDto, UpdateAgentSystemPermissionsRequestDto,
|
||||
UpdateMemoryRequestDto, UpdateProjectContextRequestDto,
|
||||
TicketUpdateRequestDto, TurnPageDto, UnassignSkillRequestDto, UpdateAgentContactableRequestDto,
|
||||
UpdateAgentContextRequestDto, UpdateAgentEffortRequestDto,
|
||||
UpdateAgentMcpToolPermissionsRequestDto, UpdateAgentPermissionsRequestDto,
|
||||
UpdateAgentSystemPermissionsRequestDto, UpdateMemoryRequestDto, UpdateProjectContextRequestDto,
|
||||
UpdateProjectMcpToolPermissionsRequestDto, UpdateProjectPermissionsRequestDto,
|
||||
UpdateProjectSystemPermissionsRequestDto, UpdateSkillRequestDto, UpdateTemplateRequestDto,
|
||||
};
|
||||
@ -3273,6 +3274,9 @@ async fn invoke(
|
||||
invoke_update_agent_permissions(&request.args, &state.app).await
|
||||
}
|
||||
"update_agent_effort" => invoke_update_agent_effort(&request.args, &state.app).await,
|
||||
"update_agent_contactable" => {
|
||||
invoke_update_agent_contactable(&request.args, &state.app).await
|
||||
}
|
||||
"resolve_agent_permissions" => {
|
||||
invoke_resolve_agent_permissions(&request.args, &state.app).await
|
||||
}
|
||||
@ -4626,6 +4630,26 @@ async fn invoke_update_agent_effort(args: &Value, state: &BackendCore) -> Result
|
||||
serde_json::to_value(output).map_err(serialization_error)
|
||||
}
|
||||
|
||||
async fn invoke_update_agent_contactable(
|
||||
args: &Value,
|
||||
state: &BackendCore,
|
||||
) -> Result<Value, ErrorDto> {
|
||||
let request =
|
||||
required_request::<UpdateAgentContactableRequestDto>("update_agent_contactable", args)?;
|
||||
let project = resolve_project_readonly(&request.project_id, state).await?;
|
||||
let output = state
|
||||
.update_agent_contactable
|
||||
.execute(UpdateAgentContactableInput {
|
||||
project,
|
||||
agent_id: parse_agent_id(&request.agent_id)?,
|
||||
contactable: request.contactable,
|
||||
})
|
||||
.await
|
||||
.map(|out| AgentDto::from_agent(out.agent))
|
||||
.map_err(ErrorDto::from)?;
|
||||
serde_json::to_value(output).map_err(serialization_error)
|
||||
}
|
||||
|
||||
async fn invoke_resolve_agent_permissions(
|
||||
args: &Value,
|
||||
state: &BackendCore,
|
||||
@ -9939,6 +9963,7 @@ mod tests {
|
||||
"update_project_permissions",
|
||||
"update_agent_permissions",
|
||||
"update_agent_effort",
|
||||
"update_agent_contactable",
|
||||
"resolve_agent_permissions",
|
||||
"get_project_system_permissions",
|
||||
"update_project_system_permissions",
|
||||
|
||||
Reference in New Issue
Block a user