feat(config): Permission-Modus pro Liste und pro Task ueberschreibbar
Bisher gab es nur AppSettings.DefaultPermissionMode global — ein Task, der plan oder acceptEdits braucht, erzwang das Umstellen der globalen Einstellung. Neue Spalten tasks.permission_mode und list_config.permission_mode, Auflösung task -> list -> global im EffectiveRunConfigResolver (den TaskRunner und get_effective_run_config gemeinsam nutzen), ComboBox mit Inherited-Badge im geteilten Agent-Editor. MigrationBaselineTests: das Fixture baute per EnsureCreated das heutige Schema und stempelte Legacy-History darauf — jede Migration nach dem Squash lief damit in 'duplicate column name'. Es migriert jetzt gezielt bis InitialCreate und prueft 'nichts pending' statt 'InitialCreate ist die einzige Zeile'.
This commit is contained in:
+9
-3
@@ -81,7 +81,8 @@ public sealed class ConfigMcpTools
|
||||
"in this list that don't override them (verify command is list-only; tasks cannot override it). Only " +
|
||||
"the fields you pass are changed; omitted fields keep their current value. To clear a field instead, " +
|
||||
"name it in clearFields; clearing all five deletes the list's config unless it also carries settings " +
|
||||
"this tool doesn't expose (session skills, file-scope serialization), which are always preserved.")]
|
||||
"this tool doesn't expose (session skills, file-scope serialization, permission mode), which are " +
|
||||
"always preserved.")]
|
||||
public async Task<SetListConfigResult> SetListConfig(
|
||||
string listId, string? model = null, string? systemPrompt = null, string? agentPath = null,
|
||||
int? maxTurns = null, string? verifyCommand = null,
|
||||
@@ -107,7 +108,8 @@ public sealed class ConfigMcpTools
|
||||
// at its default would silently reset (a SerializeOnFileOverlap reset only shows up as
|
||||
// tasks no longer serializing, long after this write).
|
||||
var hasUnrelatedSettings = existing is not null
|
||||
&& (existing.SessionSkills is not null || existing.SerializeOnFileOverlap);
|
||||
&& (existing.SessionSkills is not null || existing.SerializeOnFileOverlap
|
||||
|| existing.PermissionMode is not null);
|
||||
|
||||
ListConfigDto? config;
|
||||
var allCleared = m is null && sp is null && ap is null && mt is null && vc is null;
|
||||
@@ -127,6 +129,7 @@ public sealed class ConfigMcpTools
|
||||
ListId = listId, Model = m, SystemPrompt = sp, AgentPath = ap, MaxTurns = mt,
|
||||
VerifyCommand = vc, SessionSkills = existing?.SessionSkills,
|
||||
SerializeOnFileOverlap = existing?.SerializeOnFileOverlap ?? false,
|
||||
PermissionMode = existing?.PermissionMode,
|
||||
}, cancellationToken);
|
||||
config = allCleared ? null : new ListConfigDto(m, sp, ap, mt, vc);
|
||||
}
|
||||
@@ -158,7 +161,10 @@ public sealed class ConfigMcpTools
|
||||
var ap = clear.Contains("agentPath") ? null : agentPath.NullIfBlank() ?? task.AgentPath;
|
||||
var mt = clear.Contains("maxTurns") ? null : maxTurns ?? task.MaxTurns;
|
||||
|
||||
await _tasks.UpdateAgentSettingsAsync(taskId, m, sp, ap, mt, task.SessionSkills, cancellationToken);
|
||||
// SessionSkills and PermissionMode aren't exposed here but share the write — UpdateAgentSettings
|
||||
// sets every column, so passing the current value is what keeps them from being cleared.
|
||||
await _tasks.UpdateAgentSettingsAsync(
|
||||
taskId, m, sp, ap, mt, task.SessionSkills, task.PermissionMode, cancellationToken);
|
||||
await _broadcaster.TaskUpdated(taskId);
|
||||
return new SetTaskConfigResult(true, taskId, task.Number, new TaskConfigDto(m, sp, ap, mt));
|
||||
}
|
||||
|
||||
@@ -544,6 +544,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
var agentPath = dto.AgentPath.NullIfBlank();
|
||||
var sessionSkills = SkillsToJson(dto.SessionSkills);
|
||||
var verifyCommand = dto.VerifyCommand.NullIfBlank();
|
||||
var permissionMode = NormalizePermissionMode(dto.PermissionMode);
|
||||
|
||||
// A null SerializeOnFileOverlap means "leave it as stored" — only the list-settings modal
|
||||
// owns that field, so every other caller must not drop it (neither by deleting the row nor
|
||||
@@ -551,7 +552,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
var existing = await repo.GetConfigAsync(dto.ListId);
|
||||
var serializeOnFileOverlap = dto.SerializeOnFileOverlap ?? existing?.SerializeOnFileOverlap ?? false;
|
||||
|
||||
if (model is null && systemPrompt is null && agentPath is null && dto.MaxTurns is null && sessionSkills is null && verifyCommand is null && !serializeOnFileOverlap)
|
||||
if (model is null && systemPrompt is null && agentPath is null && dto.MaxTurns is null && sessionSkills is null && verifyCommand is null && permissionMode is null && !serializeOnFileOverlap)
|
||||
{
|
||||
await repo.DeleteConfigAsync(dto.ListId);
|
||||
}
|
||||
@@ -567,19 +568,31 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
SessionSkills = sessionSkills,
|
||||
VerifyCommand = verifyCommand,
|
||||
SerializeOnFileOverlap = serializeOnFileOverlap,
|
||||
PermissionMode = permissionMode,
|
||||
});
|
||||
}
|
||||
|
||||
await _broadcaster.ListUpdated(dto.ListId);
|
||||
}
|
||||
|
||||
/// Blank → null (inherit). Anything else must be a known mode: it becomes a
|
||||
/// <c>--permission-mode</c> argument, and an unknown value only fails once a run starts.
|
||||
private static string? NormalizePermissionMode(string? mode)
|
||||
{
|
||||
var value = mode.NullIfBlank();
|
||||
if (value is null) return null;
|
||||
if (!PermissionModeRegistry.Modes.Contains(value, StringComparer.OrdinalIgnoreCase))
|
||||
throw new HubException($"unknown permission mode: {value}");
|
||||
return value;
|
||||
}
|
||||
|
||||
public async Task<ListConfigDto?> GetListConfig(string listId)
|
||||
{
|
||||
using var ctx = _dbFactory.CreateDbContext();
|
||||
var repo = new ListRepository(ctx);
|
||||
var config = await repo.GetConfigAsync(listId);
|
||||
if (config is null) return null;
|
||||
return new ListConfigDto(config.Model, config.SystemPrompt, config.AgentPath, config.MaxTurns, SkillsFromJson(config.SessionSkills), config.VerifyCommand, config.SerializeOnFileOverlap);
|
||||
return new ListConfigDto(config.Model, config.SystemPrompt, config.AgentPath, config.MaxTurns, SkillsFromJson(config.SessionSkills), config.VerifyCommand, config.SerializeOnFileOverlap, config.PermissionMode);
|
||||
}
|
||||
|
||||
public async Task<SetTaskStatusResultDto> SetTaskStatus(string taskId, string status)
|
||||
@@ -674,7 +687,8 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
||||
dto.SystemPrompt.NullIfBlank(),
|
||||
dto.AgentPath.NullIfBlank(),
|
||||
dto.MaxTurns,
|
||||
SkillsToJson(dto.SessionSkills));
|
||||
SkillsToJson(dto.SessionSkills),
|
||||
NormalizePermissionMode(dto.PermissionMode));
|
||||
|
||||
await _broadcaster.TaskUpdated(dto.TaskId);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ public static class EffectiveRunConfigResolver
|
||||
maxTurns, maxTurnsSource, requestedMaxTurns, maxTurns < requestedMaxTurns,
|
||||
preset.Effort,
|
||||
agentPath, agentPathSource,
|
||||
PermissionModeResolver.Resolve(model, global.DefaultPermissionMode),
|
||||
PermissionModeResolver.Resolve(
|
||||
model, task.PermissionMode ?? listConfig?.PermissionMode ?? global.DefaultPermissionMode),
|
||||
systemPromptSources.Count > 0, systemPromptSources);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user