feat(FN-1050): add per-agent custom instructions support

- Add instructionsPath and instructionsText fields to Agent type and AgentStore
- Create agent-instructions resolver module in engine with priority-based resolution
- Wire custom instructions into executor, triage, reviewer, and merger agents
- Add PATCH /agents/:id/instructions API endpoint with file and text support
- Add instructions editor UI to dashboard agent detail config tab
- Add comprehensive tests for instructions resolver and AgentStore integration
- Add changeset for published package bump
This commit is contained in:
gsxdsm
2026-04-07 15:17:08 -07:00
parent 637556343a
commit 335a20e1bd
14 changed files with 775 additions and 13 deletions

View File

@@ -0,0 +1,70 @@
import { readFile } from "node:fs/promises";
import { join, isAbsolute } from "node:path";
import type { Agent } from "@fusion/core";
/**
* Resolve custom instructions for an agent by combining inline text and/or
* file-based instructions.
*
* @param agent - The agent record (may contain instructionsText and instructionsPath)
* @param rootDir - Project root directory for resolving relative paths
* @returns Concatenated instructions string, or empty string if none
*/
export async function resolveAgentInstructions(
agent: Agent | null | undefined,
rootDir: string,
): Promise<string> {
if (!agent) return "";
const parts: string[] = [];
// Inline instructions take first position
if (agent.instructionsText?.trim()) {
parts.push(agent.instructionsText.trim());
}
// File-based instructions appended after inline text
if (agent.instructionsPath?.trim()) {
const filePath = isAbsolute(agent.instructionsPath)
? agent.instructionsPath
: join(rootDir, agent.instructionsPath);
try {
const content = await readFile(filePath, "utf-8");
if (content.trim()) {
parts.push(content.trim());
}
} catch (err: unknown) {
// Graceful fallback: file doesn't exist or is unreadable
// Log a warning but don't throw — instructionsText is still used
const code = (err as NodeJS.ErrnoException).code;
if (code === "ENOENT") {
console.warn(
`[agent-instructions] Instructions file not found for agent ${agent.id}: ${filePath}`,
);
} else {
console.warn(
`[agent-instructions] Failed to read instructions file for agent ${agent.id}: ${filePath} (${code})`,
);
}
}
}
return parts.join("\n\n");
}
/**
* Append a custom instructions block to a base system prompt.
* If instructions are empty, returns the base prompt unchanged.
*
* @param basePrompt - The original system prompt
* @param instructions - Resolved instructions string
* @returns System prompt with instructions appended (if any)
*/
export function buildSystemPromptWithInstructions(
basePrompt: string,
instructions: string,
): string {
if (!instructions.trim()) return basePrompt;
return `${basePrompt}\n\n## Custom Instructions\n\n${instructions}`;
}