feat: add structured prompt invocation backend

- parse prompt tools metadata and carry structured promptInvocation payloads
- store prompt invocation metadata on trigger messages for replay-safe reruns
- route prompt agents through per-turn plan or Copilot agent overrides
- restrict prompt-scoped tools in sidecar session configuration
- auto-rescan project customization files with debounced watchers
- document the new customization watcher and prompt invocation flow

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-04-02 11:41:18 +02:00
co-authored by Copilot
parent aa7830f01a
commit 5d69d9d855
21 changed files with 900 additions and 47 deletions
@@ -10,10 +10,12 @@ internal static class AgentInstructionComposer
int agentIndex,
string workspaceKind = "project",
string interactionMode = "interactive",
string? projectInstructions = null)
string? projectInstructions = null,
RunTurnPromptInvocationDto? promptInvocation = null)
{
string baseInstructions = agent.Instructions.Trim();
string repositoryInstructions = projectInstructions?.Trim() ?? string.Empty;
string promptInvocationInstructions = FormatPromptInvocation(promptInvocation);
string workspaceGuidance = string.Equals(workspaceKind, "scratchpad", StringComparison.OrdinalIgnoreCase)
? """
You are operating in scratchpad mode.
@@ -48,10 +50,21 @@ internal static class AgentInstructionComposer
Focus on refining the answer already in progress.
""";
return JoinInstructionBlocks(baseInstructions, repositoryInstructions, workspaceGuidance, planModeGuidance, groupChatGuidance);
return JoinInstructionBlocks(
baseInstructions,
repositoryInstructions,
promptInvocationInstructions,
workspaceGuidance,
planModeGuidance,
groupChatGuidance);
}
return JoinInstructionBlocks(baseInstructions, repositoryInstructions, workspaceGuidance, planModeGuidance);
return JoinInstructionBlocks(
baseInstructions,
repositoryInstructions,
promptInvocationInstructions,
workspaceGuidance,
planModeGuidance);
}
private static string JoinInstructionBlocks(params string[] blocks)
@@ -60,4 +73,47 @@ internal static class AgentInstructionComposer
"\n\n",
blocks.Where(block => !string.IsNullOrWhiteSpace(block)).Select(block => block.Trim()));
}
private static string FormatPromptInvocation(RunTurnPromptInvocationDto? promptInvocation)
{
string? resolvedPrompt = promptInvocation?.ResolvedPrompt?.Trim();
if (string.IsNullOrWhiteSpace(resolvedPrompt))
{
return string.Empty;
}
List<string> lines =
[
"The current turn was started from a repository prompt file.",
"Treat the prompt body below as the task directive for this turn rather than as prior user chat history.",
$"Source: {promptInvocation!.SourcePath.Trim()}",
$"Name: {promptInvocation.Name.Trim()}"
];
if (!string.IsNullOrWhiteSpace(promptInvocation.Description))
{
lines.Add($"Description: {promptInvocation.Description.Trim()}");
}
if (!string.IsNullOrWhiteSpace(promptInvocation.Agent))
{
lines.Add($"Agent: {promptInvocation.Agent.Trim()}");
}
if (promptInvocation.Tools is not null)
{
List<string> toolNames = promptInvocation.Tools
.Where(tool => !string.IsNullOrWhiteSpace(tool))
.Select(tool => tool.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
lines.Add(toolNames.Count > 0
? $"Tools: {string.Join(", ", toolNames)}"
: "Tools: none");
}
lines.Add("Prompt instructions:");
lines.Add(resolvedPrompt);
return string.Join("\n", lines);
}
}