feat(backend,frontend): lot utilisateur #346 #347 #348 — exécution workspace plugin contrôlée par l'hôte, réordonnancement des onglets projet par glisser-déposer, Final restauré après stop d'agent CLI custom (QA verte)

#346 — capacités plugin SCM: le manifeste permissions.workspaceExec
(contrat SDK 8ff0f47) est appliqué côté hôte — domain PluginWorkspaceExec
Rule/Effect + audit sink, port WorkspaceToolRunner, adapter
LocalWorkspaceToolRunner sandboxé (concurrence 4, timeout, bornes
stdout/stderr, résolution bare executable sans shell), service
application + DTO backend + commandes app-tauri, gateway frontend
execTool exposée au runtime plugin — fin des lectures ad hoc du store
interne .git par les plugins.
#347 — onglets projet réordonnables par glisser-déposer (Tabs DnD,
onReorder, persistance de l'ordre via useProjects).
#348 — stop d'agent CLI custom: le message Final vert réapparaît au tour
suivant (override terminal stop armé correctement, reset de
l'interaction, refresh de la délégation requester).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-13 12:23:44 +02:00
parent 2b0d82efa3
commit 561c50f6ed
28 changed files with 1761 additions and 51 deletions

View File

@ -449,6 +449,59 @@ impl From<PluginWorkspacePathDto> for application::PluginWorkspacePathInput {
}
}
/// Permission-gated short workspace tool request DTO.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PluginWorkspaceExecDto {
/// Calling plugin id injected by the plugin runtime adapter.
pub plugin_id: String,
/// Project id.
pub project_id: String,
/// Bare executable name.
pub command: String,
/// Direct argument vector.
#[serde(default)]
pub args: Vec<String>,
/// Project-relative working directory.
#[serde(default)]
pub cwd: Option<String>,
/// Host-allowlisted environment overlay.
#[serde(default)]
pub env: std::collections::BTreeMap<String, String>,
/// Optional UTF-8 stdin.
#[serde(default)]
pub stdin: Option<String>,
/// Requested timeout.
#[serde(default)]
pub timeout_ms: Option<u64>,
/// Requested retained stdout size.
#[serde(default)]
pub max_stdout_bytes: Option<usize>,
/// Requested retained stderr size.
#[serde(default)]
pub max_stderr_bytes: Option<usize>,
}
impl From<PluginWorkspaceExecDto> for application::PluginWorkspaceExecInput {
fn from(value: PluginWorkspaceExecDto) -> Self {
Self {
plugin_id: value.plugin_id,
project_id: value.project_id,
command: value.command,
args: value.args,
cwd: value.cwd,
env: value.env.into_iter().collect(),
stdin: value.stdin,
timeout_ms: value.timeout_ms,
max_stdout_bytes: value.max_stdout_bytes,
max_stderr_bytes: value.max_stderr_bytes,
}
}
}
/// Result of a permission-gated short workspace tool invocation.
pub type PluginWorkspaceExecResultDto = application::PluginWorkspaceExecResult;
/// Plugin workspace text write request DTO.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -6558,6 +6611,52 @@ mod tests {
);
}
#[test]
fn dto_plugin_workspace_exec_maps_manifest_scoped_transport_contract() {
let dto: PluginWorkspaceExecDto = serde_json::from_value(json!({
"pluginId": "dev.acme.tools",
"projectId": Uuid::from_u128(346).to_string(),
"command": "tool",
"args": ["status", "--short"],
"cwd": ".",
"env": {"NO_COLOR": "1"},
"stdin": "input",
"timeoutMs": 5000,
"maxStdoutBytes": 256000,
"maxStderrBytes": 64000
}))
.unwrap();
let input: application::PluginWorkspaceExecInput = dto.into();
assert_eq!(input.plugin_id, "dev.acme.tools");
assert_eq!(input.command, "tool");
assert_eq!(input.args, ["status", "--short"]);
assert_eq!(input.env, [("NO_COLOR".to_owned(), "1".to_owned())]);
let result = application::PluginWorkspaceExecResult {
exit_code: Some(0),
signal: None,
timed_out: false,
stdout: "ok\n".to_owned(),
stderr: String::new(),
stdout_truncated: false,
stderr_truncated: false,
duration_ms: 8,
};
assert_eq!(
serde_json::to_value(result).unwrap(),
json!({
"exitCode": 0,
"signal": null,
"timedOut": false,
"stdout": "ok\n",
"stderr": "",
"stdoutTruncated": false,
"stderrTruncated": false,
"durationMs": 8
})
);
}
#[test]
fn dto_plugins_project_structure_query_maps_to_application_input() {
let dto = PluginProjectStructureQueryDto {

View File

@ -41,16 +41,16 @@ use application::{
OpenPluginLayoutWindow, OpenProject, OpenTerminal, OpenTicketAssistant, OrchestratorService,
PairAttemptLimiter, PairDevice, PermissionProjectorRegistry, PluginCommandTasks,
PluginConfigDocuments, PluginEventSubscriptions, PluginStorageAccess,
PluginToolchainDiagnostics, PluginWorkspaceAccess, ProjectPluginResolver, ProposeContext,
PurgeManagedTemp, QueryProjectStructure, ReadAgentContext, ReadChatDraft, ReadContext,
ReadConversationPage, ReadIssue, ReadIssueAttachment, ReadIssueCarnet, ReadMcpToolPermissions,
ReadMemory, ReadMemoryIndex, ReadOpenProjectRestoreState, ReadProjectContext,
ReadProjectSettings, ReadSkill, ReadTemplate, ReadUserSettings, RecallMemory, ReconcileLayouts,
ReconcileLiveState, ReconcileLiveStateInput, ReconcilePluginMcpServers,
RecordProjectClosedForRestore, RecordProjectOpenedForRestore, RecordTurn, RecordTurnProvider,
ReferenceProfiles, ReleaseManagedTemp, ReloadPlugin, ReloadPluginInput,
RemoveProjectFromRegistry, RenameDevice, RenameLayout, RenameSprint, ReorderSprints,
ResizeTerminal, ResolveAgentCapabilities, ResolveAgentPermissions,
PluginToolchainDiagnostics, PluginWorkspaceAccess, PluginWorkspaceExecTool,
ProjectPluginResolver, ProposeContext, PurgeManagedTemp, QueryProjectStructure,
ReadAgentContext, ReadChatDraft, ReadContext, ReadConversationPage, ReadIssue,
ReadIssueAttachment, ReadIssueCarnet, ReadMcpToolPermissions, ReadMemory, ReadMemoryIndex,
ReadOpenProjectRestoreState, ReadProjectContext, ReadProjectSettings, ReadSkill, ReadTemplate,
ReadUserSettings, RecallMemory, ReconcileLayouts, ReconcileLiveState, ReconcileLiveStateInput,
ReconcilePluginMcpServers, RecordProjectClosedForRestore, RecordProjectOpenedForRestore,
RecordTurn, RecordTurnProvider, ReferenceProfiles, ReleaseManagedTemp, ReloadPlugin,
ReloadPluginInput, RemoveProjectFromRegistry, RenameDevice, RenameLayout, RenameSprint,
ReorderSprints, ResizeTerminal, ResolveAgentCapabilities, ResolveAgentPermissions,
ResolveAgentSystemPermissions, ResolveManagedTemp, ResolveMemoryLinks, RestartCellAgentRuntime,
RestoreOpenProjects, RestoreOpenProjectsOutput, RestoreOpenWindows, RetryBackgroundTask,
RetryOpenProjectRestore, ReviewPluginPackage, RevokeAllDevices, RevokeDevice,
@ -102,20 +102,21 @@ use infrastructure::{
embedder_from_profile, AdaptiveMemoryRecall, BackgroundCompletionSink,
BackgroundTaskReadyToDeliver, ClaudePermissionProjector, ClaudeTranscriptInspector,
CliAgentRuntime, CodexPermissionProjector, CommandBackgroundRunner,
EmbeddedCompatibilityMatrix, EmbedderEnvProbe, ExternalMcpPluginSupervisor,
FsAssistantContextStore, FsBackgroundTaskStore, FsChatAttachmentStore, FsChatDraftStore,
FsConversationLog, FsDeviceSessionStore, FsEmbedderProfileStore, FsEmbedderPromptStore,
FsHandoffStore, FsIssueNumberAllocator, FsIssueStore, FsLiveStateStore, FsManagedTempStore,
FsMcpToolPermissionStore, FsMemoryStore, FsModelServerRegistry, FsOpenProjectRestoreStore,
FsOrchestratorWatcher, FsPermissionStore, FsPluginPackageStore, FsPluginRegistryStore,
FsPluginStorageStore, FsProfileStore, FsProjectPluginSettingsStore, FsProjectSettingsStore,
FsProjectStore, FsProviderSessionStore, FsScheduledProjectTaskStore, FsSecretStore,
FsSkillStore, FsSprintStore, FsSystemPermissionStore, FsTemplateStore, FsUserSettingsStore,
FsWindowStateStore, FsWorkspaceToolProvider, Git2Repository, HeuristicHandoffSummarizer,
HfModelArtifactDownloader, HttpOpenAiCompatibleProbe, HttpProviderModelCatalogue,
IdeaiContextStore, InMemoryConversationRegistry, InMemoryMailbox, InMemoryPairAttemptLimiter,
LlamaCppRuntime, LocalEnvironmentReader, LocalFileSystem, LocalManagedProcess,
LocalProcessSpawner, ManagedTempToolProvider, McpServer, MediatedInbox, NaiveMemoryRecall,
DiagnosticPluginWorkspaceExecAudit, EmbeddedCompatibilityMatrix, EmbedderEnvProbe,
ExternalMcpPluginSupervisor, FsAssistantContextStore, FsBackgroundTaskStore,
FsChatAttachmentStore, FsChatDraftStore, FsConversationLog, FsDeviceSessionStore,
FsEmbedderProfileStore, FsEmbedderPromptStore, FsHandoffStore, FsIssueNumberAllocator,
FsIssueStore, FsLiveStateStore, FsManagedTempStore, FsMcpToolPermissionStore, FsMemoryStore,
FsModelServerRegistry, FsOpenProjectRestoreStore, FsOrchestratorWatcher, FsPermissionStore,
FsPluginPackageStore, FsPluginRegistryStore, FsPluginStorageStore, FsProfileStore,
FsProjectPluginSettingsStore, FsProjectSettingsStore, FsProjectStore, FsProviderSessionStore,
FsScheduledProjectTaskStore, FsSecretStore, FsSkillStore, FsSprintStore,
FsSystemPermissionStore, FsTemplateStore, FsUserSettingsStore, FsWindowStateStore,
FsWorkspaceToolProvider, Git2Repository, HeuristicHandoffSummarizer, HfModelArtifactDownloader,
HttpOpenAiCompatibleProbe, HttpProviderModelCatalogue, IdeaiContextStore,
InMemoryConversationRegistry, InMemoryMailbox, InMemoryPairAttemptLimiter, LlamaCppRuntime,
LocalEnvironmentReader, LocalFileSystem, LocalManagedProcess, LocalProcessSpawner,
LocalWorkspaceToolRunner, ManagedTempToolProvider, McpServer, MediatedInbox, NaiveMemoryRecall,
OrchestratorWatchHandle, PortablePtyAdapter, ProcessCliVersionReader,
ReadOnlyRuntimePermissionProbe, RwFileGuard, StructuredSessionFactory, SystemClock,
SystemMillisClock, TemplateToolProvider, TicketAssistantEnvironmentPreparer,
@ -2319,6 +2320,8 @@ pub struct BackendCore {
pub reconcile_plugin_mcp_servers: Arc<ReconcilePluginMcpServers>,
/// Public plugin workspace/file access facade.
pub plugin_workspace_access: Arc<PluginWorkspaceAccess>,
/// Permission-gated short workspace tool execution facade.
pub plugin_workspace_exec: Arc<PluginWorkspaceExecTool>,
/// Public plugin structured config document facade.
pub plugin_config_documents: Arc<PluginConfigDocuments>,
/// Public plugin-owned storage facade.
@ -3855,6 +3858,17 @@ impl BackendCore {
PluginWorkspaceAccess::new(Arc::clone(&store_port), Arc::clone(&fs_port))
.with_events(Arc::clone(&events_port)),
);
let plugin_workspace_exec = Arc::new(PluginWorkspaceExecTool::new(
Arc::clone(&store_port),
Arc::clone(&fs_port),
Arc::clone(&project_plugin_resolver),
Arc::new(LocalWorkspaceToolRunner::new(
infrastructure::default_enforcer(),
)),
Arc::clone(&environment_reader_port),
Arc::clone(&clock) as Arc<dyn Clock>,
Arc::new(DiagnosticPluginWorkspaceExecAudit),
));
let plugin_config_documents = Arc::new(
PluginConfigDocuments::new(Arc::clone(&store_port), Arc::clone(&fs_port))
.with_events(Arc::clone(&events_port)),
@ -4565,6 +4579,7 @@ impl BackendCore {
list_plugin_runtime_contributions,
reconcile_plugin_mcp_servers,
plugin_workspace_access,
plugin_workspace_exec,
plugin_config_documents,
plugin_storage_access,
query_project_structure,