From 404dc2550867558f5381c45919b5290d9764a5f2 Mon Sep 17 00:00:00 2001 From: Vlad Temian Date: Wed, 28 Jan 2026 14:16:29 +0200 Subject: [PATCH 1/4] feat: add fragments support for custom agent instructions Port the fragments pattern from micode, allowing users to inject custom instructions into agent prompts via config files. Two-layer configuration: - Global: ~/.config/opencode/octto.json with fragments: { "agent": ["..."] } - Project: .octto/fragments.json with same structure Global fragments load first, project fragments append. Fragments are formatted as XML blocks prepended to agent system prompts. Includes: - FragmentsSchema validation in config - Fragment-injector hook with Levenshtein distance typo detection - Comprehensive test coverage Closes #3 Co-Authored-By: Claude Opus 4.5 --- src/config/index.ts | 4 +- src/config/loader.ts | 6 +- src/config/schema.ts | 4 + src/hooks/fragment-injector.ts | 171 +++++++++++++++++++++ src/hooks/index.ts | 11 ++ src/index.ts | 20 ++- tests/config/schema.test.ts | 84 ++++++++++- tests/hooks/fragment-injector.test.ts | 208 ++++++++++++++++++++++++++ 8 files changed, 501 insertions(+), 7 deletions(-) create mode 100644 src/hooks/fragment-injector.ts create mode 100644 src/hooks/index.ts create mode 100644 tests/hooks/fragment-injector.test.ts diff --git a/src/config/index.ts b/src/config/index.ts index cfe72a9..59ac9ff 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,3 +1,3 @@ -export type { AgentOverride, CustomConfig, OcttoConfig } from "./loader"; +export type { AgentOverride, CustomConfig, Fragments, OcttoConfig } from "./loader"; export { loadCustomConfig, resolvePort } from "./loader"; -export { AgentOverrideSchema, OcttoConfigSchema, PortSchema } from "./schema"; +export { AgentOverrideSchema, FragmentsSchema, OcttoConfigSchema, PortSchema } from "./schema"; diff --git a/src/config/loader.ts b/src/config/loader.ts index a54a535..00f3f6b 100644 --- a/src/config/loader.ts +++ b/src/config/loader.ts @@ -7,9 +7,9 @@ import * as v from "valibot"; import { AGENTS } from "@/agents"; -import { AgentOverrideSchema, type OcttoConfig, OcttoConfigSchema } from "./schema"; +import { AgentOverrideSchema, type Fragments, type OcttoConfig, OcttoConfigSchema } from "./schema"; -export type { AgentOverride, OcttoConfig } from "./schema"; +export type { AgentOverride, Fragments, OcttoConfig } from "./schema"; const OCTTO_PORT_ENV = "OCTTO_PORT"; const DEFAULT_PORT = 0; @@ -111,6 +111,7 @@ async function load(configDir?: string): Promise { export interface CustomConfig { agents: Record; port: number; + fragments: Fragments; } /** @@ -130,5 +131,6 @@ export async function loadCustomConfig(agents: Record, conf return { agents: mergedAgents, port: resolvePort(config?.port), + fragments: config?.fragments, }; } diff --git a/src/config/schema.ts b/src/config/schema.ts index 50fa503..c5094f2 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -12,10 +12,14 @@ export const AgentOverrideSchema = v.partial( export const PortSchema = v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(65535)); +export const FragmentsSchema = v.optional(v.record(v.enum(AGENTS), v.array(v.string()))); + export const OcttoConfigSchema = v.object({ agents: v.optional(v.record(v.enum(AGENTS), AgentOverrideSchema)), port: v.optional(PortSchema), + fragments: FragmentsSchema, }); export type AgentOverride = v.InferOutput; +export type Fragments = v.InferOutput; export type OcttoConfig = v.InferOutput; diff --git a/src/hooks/fragment-injector.ts b/src/hooks/fragment-injector.ts new file mode 100644 index 0000000..80932d0 --- /dev/null +++ b/src/hooks/fragment-injector.ts @@ -0,0 +1,171 @@ +// src/hooks/fragment-injector.ts +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; + +import * as v from "valibot"; + +import { AGENTS } from "@/agents"; + +type FragmentsRecord = Record | undefined; + +const VALID_AGENT_NAMES = Object.values(AGENTS); + +const ProjectFragmentsSchema = v.record(v.string(), v.array(v.string())); + +/** + * Format fragments array as an XML block to prepend to agent prompts. + */ +export function formatFragmentsBlock(fragments: string[] | undefined): string { + if (!fragments || fragments.length === 0) { + return ""; + } + + const bulletPoints = fragments.map((f) => `- ${f}`).join("\n"); + return `\n${bulletPoints}\n\n\n`; +} + +/** + * Merge global and project fragments. + * Global fragments come first, project fragments append. + */ +export function mergeFragments(global: FragmentsRecord, project: FragmentsRecord): Record { + const result: Record = {}; + + if (global) { + for (const [agent, frags] of Object.entries(global)) { + result[agent] = [...frags]; + } + } + + if (project) { + for (const [agent, frags] of Object.entries(project)) { + if (result[agent]) { + result[agent].push(...frags); + } else { + result[agent] = [...frags]; + } + } + } + + return result; +} + +/** + * Load project-level fragments from .octto/fragments.json + */ +export async function loadProjectFragments(projectDir: string): Promise | undefined> { + const fragmentsPath = join(projectDir, ".octto", "fragments.json"); + + try { + const content = await readFile(fragmentsPath, "utf-8"); + const parsed = JSON.parse(content); + + const result = v.safeParse(ProjectFragmentsSchema, parsed); + if (!result.success) { + console.warn(`[octto] Invalid fragments.json schema in ${fragmentsPath}`); + return undefined; + } + + return result.output; + } catch { + return undefined; + } +} + +/** + * Calculate Levenshtein distance between two strings. + * Used for suggesting similar agent names for typos. + */ +export function levenshteinDistance(a: string, b: string): number { + if (a.length === 0) return b.length; + if (b.length === 0) return a.length; + + const matrix: number[][] = []; + + for (let i = 0; i <= b.length; i++) { + matrix[i] = [i]; + } + + for (let j = 0; j <= a.length; j++) { + matrix[0][j] = j; + } + + for (let i = 1; i <= b.length; i++) { + for (let j = 1; j <= a.length; j++) { + if (b.charAt(i - 1) === a.charAt(j - 1)) { + matrix[i][j] = matrix[i - 1][j - 1]; + } else { + matrix[i][j] = Math.min( + matrix[i - 1][j - 1] + 1, // substitution + matrix[i][j - 1] + 1, // insertion + matrix[i - 1][j] + 1, // deletion + ); + } + } + } + + return matrix[b.length][a.length]; +} + +/** + * Warn about unknown agent names in fragments config. + * Suggests similar valid agent names for likely typos. + */ +export function warnUnknownAgents(fragments: Record | undefined): void { + if (!fragments) return; + + for (const agentName of Object.keys(fragments)) { + if (VALID_AGENT_NAMES.includes(agentName as AGENTS)) { + continue; + } + + // Find closest valid agent name + let closest: string | undefined; + let minDistance = Infinity; + + for (const validName of VALID_AGENT_NAMES) { + const distance = levenshteinDistance(agentName, validName); + if (distance < minDistance && distance <= 3) { + minDistance = distance; + closest = validName; + } + } + + let message = `[octto] Unknown agent "${agentName}" in fragments config.`; + if (closest) { + message += ` Did you mean "${closest}"?`; + } + message += ` Valid agents: ${VALID_AGENT_NAMES.join(", ")}`; + + console.warn(message); + } +} + +export interface FragmentInjectorContext { + projectDir: string; +} + +/** + * Create a fragment injector that can modify agent system prompts. + * Returns merged fragments from global config and project config. + */ +export async function createFragmentInjector( + ctx: FragmentInjectorContext, + globalFragments: FragmentsRecord, +): Promise> { + const projectFragments = await loadProjectFragments(ctx.projectDir); + const merged = mergeFragments(globalFragments, projectFragments); + + // Warn about unknown agents in both global and project fragments + warnUnknownAgents(globalFragments); + warnUnknownAgents(projectFragments); + + return merged; +} + +/** + * Get the system prompt prefix for a specific agent. + */ +export function getAgentSystemPromptPrefix(fragments: Record, agentName: string): string { + return formatFragmentsBlock(fragments[agentName]); +} diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..73fbd71 --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,11 @@ +// src/hooks/index.ts +export { + createFragmentInjector, + type FragmentInjectorContext, + formatFragmentsBlock, + getAgentSystemPromptPrefix, + levenshteinDistance, + loadProjectFragments, + mergeFragments, + warnUnknownAgents, +} from "./fragment-injector"; diff --git a/src/index.ts b/src/index.ts index 6f50334..72cb62e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,13 +2,20 @@ import type { Plugin } from "@opencode-ai/plugin"; -import { agents } from "@/agents"; +import { AGENTS, agents } from "@/agents"; import { loadCustomConfig } from "@/config"; +import { createFragmentInjector, getAgentSystemPromptPrefix, warnUnknownAgents } from "@/hooks"; import { createSessionStore } from "@/session"; import { createOcttoTools } from "@/tools"; -const Octto: Plugin = async ({ client }) => { +const Octto: Plugin = async ({ client, directory }) => { const customConfig = await loadCustomConfig(agents); + + // Load and merge fragments from global config and project config + const fragments = await createFragmentInjector({ projectDir: directory }, customConfig.fragments); + + // Warn about unknown agent names in global config at startup + warnUnknownAgents(customConfig.fragments); const sessions = createSessionStore({ port: customConfig.port }); const tracked = new Map>(); const tools = createOcttoTools(sessions, client); @@ -32,7 +39,16 @@ const Octto: Plugin = async ({ client }) => { tool: tools, config: async (config) => { + // Apply agent overrides from custom config config.agent = { ...config.agent, ...customConfig.agents }; + + // Inject fragments into agent prompts + for (const agentName of Object.values(AGENTS)) { + const prefix = getAgentSystemPromptPrefix(fragments, agentName); + if (prefix && config.agent[agentName]?.prompt) { + config.agent[agentName].prompt = prefix + config.agent[agentName].prompt; + } + } }, event: async ({ event }) => { diff --git a/tests/config/schema.test.ts b/tests/config/schema.test.ts index 31928ea..3991fe5 100644 --- a/tests/config/schema.test.ts +++ b/tests/config/schema.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from "bun:test"; import * as v from "valibot"; -import { OcttoConfigSchema } from "../../src/config/schema"; +import { FragmentsSchema, OcttoConfigSchema } from "../../src/config/schema"; describe("OcttoConfigSchema", () => { describe("port field", () => { @@ -54,4 +54,86 @@ describe("OcttoConfigSchema", () => { } }); }); + + describe("fragments field", () => { + it("should accept valid fragments for known agents", () => { + const result = v.safeParse(OcttoConfigSchema, { + fragments: { + octto: ["instruction 1", "instruction 2"], + }, + }); + expect(result.success).toBe(true); + if (result.success) { + expect(result.output.fragments?.octto).toEqual(["instruction 1", "instruction 2"]); + } + }); + + it("should accept fragments for multiple agents", () => { + const result = v.safeParse(OcttoConfigSchema, { + fragments: { + octto: ["octto instruction"], + bootstrapper: ["bootstrapper instruction"], + probe: ["probe instruction"], + }, + }); + expect(result.success).toBe(true); + if (result.success) { + expect(result.output.fragments?.octto).toEqual(["octto instruction"]); + expect(result.output.fragments?.bootstrapper).toEqual(["bootstrapper instruction"]); + expect(result.output.fragments?.probe).toEqual(["probe instruction"]); + } + }); + + it("should accept empty fragments array", () => { + const result = v.safeParse(OcttoConfigSchema, { + fragments: { + octto: [], + }, + }); + expect(result.success).toBe(true); + if (result.success) { + expect(result.output.fragments?.octto).toEqual([]); + } + }); + + it("should allow config without fragments (optional)", () => { + const result = v.safeParse(OcttoConfigSchema, {}); + expect(result.success).toBe(true); + if (result.success) { + expect(result.output.fragments).toBeUndefined(); + } + }); + + it("should reject unknown agent names in fragments", () => { + const result = v.safeParse(OcttoConfigSchema, { + fragments: { + unknown_agent: ["instruction"], + }, + }); + expect(result.success).toBe(false); + }); + + it("should reject non-string values in fragments array", () => { + const result = v.safeParse(OcttoConfigSchema, { + fragments: { + octto: [123, "valid"], + }, + }); + expect(result.success).toBe(false); + }); + }); +}); + +describe("FragmentsSchema", () => { + it("should be optional", () => { + const result = v.safeParse(FragmentsSchema, undefined); + expect(result.success).toBe(true); + }); + + it("should accept valid fragment record", () => { + const result = v.safeParse(FragmentsSchema, { + octto: ["instruction"], + }); + expect(result.success).toBe(true); + }); }); diff --git a/tests/hooks/fragment-injector.test.ts b/tests/hooks/fragment-injector.test.ts new file mode 100644 index 0000000..34ff5b0 --- /dev/null +++ b/tests/hooks/fragment-injector.test.ts @@ -0,0 +1,208 @@ +// tests/hooks/fragment-injector.test.ts +import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test"; +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import { AGENTS } from "../../src/agents"; +import { + formatFragmentsBlock, + levenshteinDistance, + loadProjectFragments, + mergeFragments, + warnUnknownAgents, +} from "../../src/hooks/fragment-injector"; + +describe("formatFragmentsBlock", () => { + it("should return empty string for undefined fragments", () => { + expect(formatFragmentsBlock(undefined)).toBe(""); + }); + + it("should return empty string for empty array", () => { + expect(formatFragmentsBlock([])).toBe(""); + }); + + it("should format single fragment as XML block", () => { + const result = formatFragmentsBlock(["Custom instruction"]); + expect(result).toBe("\n- Custom instruction\n\n\n"); + }); + + it("should format multiple fragments as XML block with bullet points", () => { + const result = formatFragmentsBlock(["Instruction 1", "Instruction 2", "Instruction 3"]); + expect(result).toBe( + "\n- Instruction 1\n- Instruction 2\n- Instruction 3\n\n\n", + ); + }); +}); + +describe("mergeFragments", () => { + it("should return empty object when both are undefined", () => { + expect(mergeFragments(undefined, undefined)).toEqual({}); + }); + + it("should return global fragments when project is undefined", () => { + const global = { octto: ["global instruction"] }; + expect(mergeFragments(global, undefined)).toEqual({ octto: ["global instruction"] }); + }); + + it("should return project fragments when global is undefined", () => { + const project = { octto: ["project instruction"] }; + expect(mergeFragments(undefined, project)).toEqual({ octto: ["project instruction"] }); + }); + + it("should merge fragments for same agent (global first, project appended)", () => { + const global = { octto: ["global instruction"] }; + const project = { octto: ["project instruction"] }; + expect(mergeFragments(global, project)).toEqual({ + octto: ["global instruction", "project instruction"], + }); + }); + + it("should keep fragments for different agents separate", () => { + const global = { octto: ["octto instruction"] }; + const project = { bootstrapper: ["bootstrapper instruction"] }; + expect(mergeFragments(global, project)).toEqual({ + octto: ["octto instruction"], + bootstrapper: ["bootstrapper instruction"], + }); + }); + + it("should merge complex multi-agent fragments", () => { + const global = { + octto: ["octto global 1", "octto global 2"], + probe: ["probe global"], + }; + const project = { + octto: ["octto project"], + bootstrapper: ["bootstrapper project"], + }; + expect(mergeFragments(global, project)).toEqual({ + octto: ["octto global 1", "octto global 2", "octto project"], + probe: ["probe global"], + bootstrapper: ["bootstrapper project"], + }); + }); +}); + +describe("loadProjectFragments", () => { + let tempDir: string; + + beforeEach(async () => { + tempDir = await mkdtemp(join(tmpdir(), "octto-test-")); + }); + + afterEach(async () => { + await rm(tempDir, { recursive: true }); + }); + + it("should return undefined when .octto directory does not exist", async () => { + const result = await loadProjectFragments(tempDir); + expect(result).toBeUndefined(); + }); + + it("should return undefined when fragments.json does not exist", async () => { + await mkdir(join(tempDir, ".octto")); + const result = await loadProjectFragments(tempDir); + expect(result).toBeUndefined(); + }); + + it("should load valid fragments.json", async () => { + await mkdir(join(tempDir, ".octto")); + await writeFile(join(tempDir, ".octto", "fragments.json"), JSON.stringify({ octto: ["project instruction"] })); + + const result = await loadProjectFragments(tempDir); + expect(result).toEqual({ octto: ["project instruction"] }); + }); + + it("should return undefined for invalid JSON", async () => { + await mkdir(join(tempDir, ".octto")); + await writeFile(join(tempDir, ".octto", "fragments.json"), "not valid json"); + + const result = await loadProjectFragments(tempDir); + expect(result).toBeUndefined(); + }); + + it("should return undefined for invalid schema (wrong type)", async () => { + await mkdir(join(tempDir, ".octto")); + await writeFile(join(tempDir, ".octto", "fragments.json"), JSON.stringify({ octto: "not an array" })); + + const result = await loadProjectFragments(tempDir); + expect(result).toBeUndefined(); + }); +}); + +describe("levenshteinDistance", () => { + it("should return 0 for identical strings", () => { + expect(levenshteinDistance("octto", "octto")).toBe(0); + }); + + it("should return length of b for empty a", () => { + expect(levenshteinDistance("", "octto")).toBe(5); + }); + + it("should return length of a for empty b", () => { + expect(levenshteinDistance("octto", "")).toBe(5); + }); + + it("should calculate single character substitution", () => { + expect(levenshteinDistance("octto", "octta")).toBe(1); + }); + + it("should calculate single character insertion", () => { + expect(levenshteinDistance("octto", "octtoo")).toBe(1); + }); + + it("should calculate single character deletion", () => { + expect(levenshteinDistance("octto", "octt")).toBe(1); + }); + + it("should handle typical typos", () => { + expect(levenshteinDistance("octto", "octo")).toBe(1); // missing t + expect(levenshteinDistance("bootstrapper", "bootsrapper")).toBe(1); // typo + }); +}); + +describe("warnUnknownAgents", () => { + let consoleSpy: ReturnType; + + beforeEach(() => { + consoleSpy = spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + consoleSpy.mockRestore(); + }); + + it("should not warn for valid agent names", () => { + warnUnknownAgents({ octto: ["instruction"], bootstrapper: ["instruction"] }); + expect(consoleSpy).not.toHaveBeenCalled(); + }); + + it("should not warn for undefined fragments", () => { + warnUnknownAgents(undefined); + expect(consoleSpy).not.toHaveBeenCalled(); + }); + + it("should not warn for empty fragments", () => { + warnUnknownAgents({}); + expect(consoleSpy).not.toHaveBeenCalled(); + }); + + it("should warn for unknown agent name", () => { + warnUnknownAgents({ unknown_agent: ["instruction"] } as any); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('[octto] Unknown agent "unknown_agent" in fragments'), + ); + }); + + it("should suggest similar agent name for typos", () => { + warnUnknownAgents({ octo: ["instruction"] } as any); + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Did you mean "octto"?')); + }); + + it("should suggest multiple similar names when applicable", () => { + // "prob" is close to "probe" + warnUnknownAgents({ prob: ["instruction"] } as any); + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Did you mean "probe"?')); + }); +}); From 7643814cf5bc84a6c1d5a24aedc564b5998f90ce Mon Sep 17 00:00:00 2001 From: Vlad Temian Date: Wed, 28 Jan 2026 14:36:58 +0200 Subject: [PATCH 2/4] fix: inject fragments at agent source, not just config hook The config hook modification wasn't affecting brainstorming sessions because they use customConfig.agents directly. Now fragments are injected into customConfig.agents at plugin load time. Co-Authored-By: Claude Opus 4.5 --- src/index.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 72cb62e..1ec756c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,14 @@ const Octto: Plugin = async ({ client, directory }) => { // Load and merge fragments from global config and project config const fragments = await createFragmentInjector({ projectDir: directory }, customConfig.fragments); + // Inject fragments into agent prompts at the source + for (const agentName of Object.values(AGENTS)) { + const prefix = getAgentSystemPromptPrefix(fragments, agentName); + if (prefix && customConfig.agents[agentName]?.prompt) { + customConfig.agents[agentName].prompt = prefix + customConfig.agents[agentName].prompt; + } + } + // Warn about unknown agent names in global config at startup warnUnknownAgents(customConfig.fragments); const sessions = createSessionStore({ port: customConfig.port }); @@ -42,7 +50,7 @@ const Octto: Plugin = async ({ client, directory }) => { // Apply agent overrides from custom config config.agent = { ...config.agent, ...customConfig.agents }; - // Inject fragments into agent prompts + // Inject fragments into agent prompts (for opencode config) for (const agentName of Object.values(AGENTS)) { const prefix = getAgentSystemPromptPrefix(fragments, agentName); if (prefix && config.agent[agentName]?.prompt) { From 21cf7630a419ca36c0ade1ec6f5837296d6b64b1 Mon Sep 17 00:00:00 2001 From: Vlad Temian Date: Wed, 28 Jan 2026 14:37:48 +0200 Subject: [PATCH 3/4] docs: add fragments configuration to README Co-Authored-By: Claude Opus 4.5 --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index d6c9829..672f90f 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,34 @@ Optional `~/.config/opencode/octto.json`: |--------|------|---------|-------------| | `port` | number | `0` (random) | Fixed port for the browser UI server | | `agents` | object | - | Override agent models/settings | +| `fragments` | object | - | Custom instructions injected into agent prompts | + +### Fragments + +Inject custom instructions into agent prompts. Useful for customizing agent behavior per-project or globally. + +**Global config** (`~/.config/opencode/octto.json`): + +```json +{ + "fragments": { + "octto": ["Always suggest 3 implementation approaches"], + "probe": ["Include emoji in every question"], + "bootstrapper": ["Focus on technical feasibility"] + } +} +``` + +**Project config** (`.octto/fragments.json` in your project root): + +```json +{ + "octto": ["This project uses React - focus on component patterns"], + "probe": ["Ask about testing strategy for each feature"] +} +``` + +Fragments are merged: global fragments load first, project fragments append. Each fragment becomes a bullet point in a `` block prepended to the agent's system prompt. ### Environment Variables From 9758f1b6ce8a1f6994529291c85745090f1d3e78 Mon Sep 17 00:00:00 2001 From: Vlad Temian Date: Wed, 28 Jan 2026 14:46:59 +0200 Subject: [PATCH 4/4] fix: remove duplicate fragment injection in config hook Fragments are already injected into customConfig.agents at plugin load. The config hook was injecting them again after spreading customConfig.agents into config.agent, causing duplicate instructions. Co-Authored-By: Claude Opus 4.5 --- src/index.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1ec756c..71c9e39 100644 --- a/src/index.ts +++ b/src/index.ts @@ -47,16 +47,8 @@ const Octto: Plugin = async ({ client, directory }) => { tool: tools, config: async (config) => { - // Apply agent overrides from custom config + // Apply agent overrides from custom config (fragments already injected at plugin load) config.agent = { ...config.agent, ...customConfig.agents }; - - // Inject fragments into agent prompts (for opencode config) - for (const agentName of Object.values(AGENTS)) { - const prefix = getAgentSystemPromptPrefix(fragments, agentName); - if (prefix && config.agent[agentName]?.prompt) { - config.agent[agentName].prompt = prefix + config.agent[agentName].prompt; - } - } }, event: async ({ event }) => {