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:
@ -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 {
|
||||
|
||||
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user