feat(worker): PowerUse-Addendum an die Handler-Prompts, Cap aus den Settings
HandoffRoundTracker.Register nimmt den Cap jetzt als Parameter statt aus einer Konstante; HandoffMcpTools liest ihn pro Aufruf aus app_settings. Werte unter 1 werden auf 1 geklemmt (in WorkerHub UND in Register): der Cap ist das Einzige, was eine entlaufene Handoff-Kette begrenzt, "0 = unbegrenzt" darf kein Aufrufer waehlen koennen. InteractiveLaunchSpecService.ComposeHandlerPromptAsync haengt das PowerUse- Addendum an den Phasen-Prompt, aber nur wenn globaler Schalter und Listen-Opt-in beide gesetzt sind. Bewusst ein Addendum statt einer zweiten Prompt-Garnitur: die gemeinsamen 95% bleiben an einer Stelle, und ein Run ohne PowerUse bekommt einen byte-identischen Prompt wie vorher. Das Addendum ist als Override der interaktiven Regeln formuliert (nie fragen, entscheiden und protokollieren; Konflikte loesen oder abbrechen, nie im Tree liegen lassen; Arbeit ernten, wenn eine Runde sonst leer laeuft). Drei Tests pinnen alle vier Ecken des UND, weil die Sicherheitsaussage des Features genau daran haengt.
This commit is contained in:
@@ -296,7 +296,8 @@ public sealed class InteractiveLaunchSpecService
|
||||
Directory.CreateDirectory(sessionDir);
|
||||
|
||||
var systemPromptPath = Path.Combine(sessionDir, "system-prompt.md");
|
||||
await File.WriteAllTextAsync(systemPromptPath, PromptFiles.ReadOrDefault(PromptKind.MergeHelperTriage), ct);
|
||||
await File.WriteAllTextAsync(systemPromptPath,
|
||||
await ComposeHandlerPromptAsync(ctx, listId, PromptKind.MergeHelperTriage, ct), ct);
|
||||
|
||||
var briefPath = Path.Combine(sessionDir, "brief.md");
|
||||
await File.WriteAllTextAsync(briefPath, PromptFiles.Render(PromptKind.MergeHelperInitial,
|
||||
@@ -391,7 +392,8 @@ public sealed class InteractiveLaunchSpecService
|
||||
Directory.CreateDirectory(sessionDir);
|
||||
|
||||
var systemPromptPath = Path.Combine(sessionDir, "system-prompt.md");
|
||||
await File.WriteAllTextAsync(systemPromptPath, PromptFiles.ReadOrDefault(phase.PromptKind), ct);
|
||||
await File.WriteAllTextAsync(systemPromptPath,
|
||||
await ComposeHandlerPromptAsync(ctx, handlerTask.ListId, phase.PromptKind, ct), ct);
|
||||
|
||||
// A "_final" round differs from its non-final counterpart in exactly one rendered line --
|
||||
// the same system prompt and model drive both -- marking the final round and forbidding a
|
||||
@@ -554,6 +556,31 @@ public sealed class InteractiveLaunchSpecService
|
||||
return handlerTask.Id;
|
||||
}
|
||||
|
||||
// The list-handler system prompt, plus the PowerUse addendum when this run is in PowerUse.
|
||||
//
|
||||
// PowerUse requires BOTH the global switch (AppSettingsEntity.PowerUseEnabled, toggled on the
|
||||
// usage monitor) AND the list's own opt-in (ListConfigEntity.PowerUseAllowed). That is the whole
|
||||
// point of the two-key design: the global switch can be left on overnight without turning some
|
||||
// unrelated list's handler run unattended, because a list that never opted in keeps the
|
||||
// interactive prompt unchanged.
|
||||
//
|
||||
// Appending rather than substituting a second prompt set keeps the shared 95% in one place --
|
||||
// the addendum is written to read as an override of the base prompt's interactive rules. A run
|
||||
// that is not in PowerUse gets a byte-identical prompt to what it got before PowerUse existed.
|
||||
private static async Task<string> ComposeHandlerPromptAsync(
|
||||
ClaudeDoDbContext ctx, string listId, PromptKind kind, CancellationToken ct)
|
||||
{
|
||||
var basePrompt = PromptFiles.ReadOrDefault(kind);
|
||||
|
||||
var settings = await new AppSettingsRepository(ctx).GetAsync(ct);
|
||||
if (!settings.PowerUseEnabled) return basePrompt;
|
||||
|
||||
var listConfig = await new ListRepository(ctx).GetConfigAsync(listId, ct);
|
||||
if (listConfig?.PowerUseAllowed != true) return basePrompt;
|
||||
|
||||
return basePrompt + "\n\n" + PromptFiles.ReadOrDefault(PromptKind.PowerUseAddendum);
|
||||
}
|
||||
|
||||
// Maps a handoff nextPhase to the system prompt/model the next session runs under, plus the
|
||||
// "phase N" label used in its short positional kickoff. wait/wait_final share MergeHelperWait
|
||||
// + HandlerWaitAlias; merge/merge_final share MergeHelperMerge + HandlerMergeAlias -- only
|
||||
|
||||
Reference in New Issue
Block a user