@maestria/opencode 0.7.6 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/package.json +1 -1
- package/dist/index.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,4 @@ import { Plugin } from "@opencode-ai/plugin";
|
|
|
2
2
|
//#region src/index.d.ts
|
|
3
3
|
declare const MaestriaPlugin: Plugin;
|
|
4
4
|
//#endregion
|
|
5
|
-
export { MaestriaPlugin, MaestriaPlugin as default };
|
|
6
|
-
//# sourceMappingURL=index.d.ts.map
|
|
5
|
+
export { MaestriaPlugin, MaestriaPlugin as default };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import{merge as e}from"es-toolkit";import{readFileSync as t,readdirSync as n}from"node:fs";import r from"node:path";import{parse as i}from"yaml";import{z as a}from"zod";const o=[`fein`,`sonar`,`blitz`],s={blitz:`[MODE: blitz]`,fein:`[MODE: fein]`,sonar:`[MODE: sonar]`},c={blitz:1,fein:3,sonar:2},l=/```[\s\S]*?```|`[^`]*`/gu,u=e=>{let t=[],n;for(l.lastIndex=0;(n=l.exec(e))!==null;)t.push([n.index,n.index+n[0].length]);return t},d=(e,t)=>t.some(([t,n])=>e>=t&&e<n),f=e=>{let t=e.indexOf(`## MODE:`);return t===-1?`${e.replace(/\s+$/u,``)}\n`:`${e.slice(t).replace(/\s+$/u,``)}\n`},p=e=>e.replaceAll(/[.*+?^${}()|[\]\\]/gu,`\\$&`),m=e=>RegExp(`\\b${p(e)}\\b`,`giu`),h=e=>o.includes(e),g=e=>h(e)?s[e]:``,_=(e,t)=>{if(!e)return null;let n=u(e),r=t?new Set([...t].map(e=>e.toLowerCase())):void 0,i=null;for(let t of o){if(r!=null&&r.has(t))continue;let a=m(t),o;for(;(o=a.exec(e))!==null;)d(o.index,n)||(i===null||c[t]>c[i.mode])&&(i={index:o.index,keyword:o[0],mode:t})}return i},v=(e,t)=>(e.slice(0,t.index)+e.slice(t.index+t.keyword.length).replace(/^:\s*/u,``)).replaceAll(/ {2,}/gu,` `).trim(),y=import.meta.dirname,b=r.resolve(y,`..`),x=r.join(b,`agents`),S=r.join(b,`agents`,`commands`),C=r.join(b,`rules`,`AGENTS.md`),w=[`fein`,`sonar`,`blitz`],T=e=>{let n=t(r.resolve(S,`${e}.md`),`utf-8`);return f(n)},E=e=>w.some(t=>t===e),D=new Proxy({},{get(e,t,n){if(typeof t==`string`&&E(t)){if(!(t in e))try{e[t]=T(t)}catch(n){console.warn(`[maestria] Failed to load mode prompt "${t}":`,n),e[t]=``}return e[t]}return Reflect.get(e,t,n)}}),O={blitz:`[MODE: blitz]`,fein:`[MODE: fein]`,sonar:`[MODE: sonar]`},k=e=>w.includes(e),A=(e,t)=>{let n=_(e,t);return n===null?null:{index:n.index,keyword:n.keyword,marker:O[n.mode],mode:n.mode,prompt:D[n.mode]}},j=(e,t)=>v(e,t),M=e=>k(e)?D[e]:``,N=e=>k(e)?g(e):``,P=a.enum([`fein`,`sonar`,`blitz`]),F=a.object({modes:a.object({disabledKeywords:a.array(P).optional()}).optional()}),I=e=>typeof e==`object`&&!!e&&!Array.isArray(e),L=e=>e===`all`||e===`primary`||e===`subagent`,R=e=>{if(!I(e))return{};let t={};for(let[n,r]of Object.entries(e))t[n]=r;return t},z=e=>{let t=i(e),n=I(t)?t:{};return{color:typeof n.color==`string`?n.color:void 0,description:typeof n.description==`string`?n.description:``,maxSteps:n.maxSteps!==void 0&&n.maxSteps!==null&&n.maxSteps!==``?Number(n.maxSteps):void 0,mode:L(n.mode)?n.mode:`subagent`,permission:R(n.permission)}},B=e=>{let n=t(e,`utf-8`),i=r.basename(e,`.md`),a=n.split(`---`);if(a.length<3)throw Error(`Invalid agent file: ${e} - missing frontmatter`);let o=z(a[1].trim()),s=a.slice(2).join(`---`).trim(),c={description:o.description,mode:o.mode,permission:o.permission,prompt:s};return o.color!==void 0&&o.color!==null&&o.color!==``&&(c.color=o.color),o.maxSteps!==void 0&&o.maxSteps!==null&&o.maxSteps!==0&&(c.maxSteps=o.maxSteps),{config:c,name:i}},V=()=>{try{let e=n(x).filter(e=>e.endsWith(`.md`)),t={};for(let n of e)try{let{name:e,config:i}=B(r.join(x,n));t[e]=i}catch(e){console.warn(`[maestria] Failed to parse agent file "${n}":`,e)}return t}catch(e){throw console.error(`[maestria] Failed to read agents directory:`,e),Error(`[maestria] Failed to load agents from "${x}": ${e instanceof Error?e.message:String(e)}`,{cause:e})}},H=(e,t,n)=>{if(e.agent!==`orchestrator`)return;let r=t.parts.find(e=>e.type===`text`);if(r===void 0)return;let i=A(r.text,n);i!==null&&(r.text=[N(i.mode),``,M(i.mode),``,j(r.text,i)].join(`
|
|
2
|
-
`))},U=(t,n)=>{t.agent=e(t.agent??{},n),t.instructions=[...t.instructions??[],C]},W=e=>{e.context.push(`Session was compacted. Task tracking is maintained via todowrite. Active context (files, decisions, blockers) was captured before compaction. Continue where you left off.`)},G=async(e,t)=>{let n=F.parse(t??{}),r=new Set((n.modes?.disabledKeywords??[]).map(e=>e.toLowerCase())),i=V();return await Promise.resolve(),{"chat.message":async(e,t)=>{H(e,t,r),await Promise.resolve()},config:async e=>{U(e,i),await Promise.resolve()},"experimental.session.compacting":async(e,t)=>{W(t),await Promise.resolve()}}};export{G as MaestriaPlugin,G as default};
|
|
3
|
-
//# sourceMappingURL=index.js.map
|
|
2
|
+
`))},U=(t,n)=>{t.agent=e(t.agent??{},n),t.instructions=[...t.instructions??[],C]},W=e=>{e.context.push(`Session was compacted. Task tracking is maintained via todowrite. Active context (files, decisions, blockers) was captured before compaction. Continue where you left off.`)},G=async(e,t)=>{let n=F.parse(t??{}),r=new Set((n.modes?.disabledKeywords??[]).map(e=>e.toLowerCase())),i=V();return await Promise.resolve(),{"chat.message":async(e,t)=>{H(e,t,r),await Promise.resolve()},config:async e=>{U(e,i),await Promise.resolve()},"experimental.session.compacting":async(e,t)=>{W(t),await Promise.resolve()}}};export{G as MaestriaPlugin,G as default};
|
package/package.json
CHANGED
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["MODE_MARKERS","isModeKeyword","getModeMarker","detectMode","stripKeyword","isModeKeyword","sharedDetectMode","sharedStripKeyword","sharedGetMarker","parseYaml"],"sources":["../../shared/mode/src/index.ts","../src/root.ts","../src/modes/prompts.ts","../src/modes/index.ts","../src/modes/types.ts","../src/index.ts"],"sourcesContent":["/**\n * Private runtime-neutral shared mode mechanics.\n *\n * Pure TypeScript - no host SDKs, no filesystem APIs.\n * Consolidates the duplicated pure logic from:\n * - `packages/opencode/src/modes/index.ts`\n * - `packages/shared/pi/src/modes-core.ts`\n *\n * Consumers (OpenCode, shared-pi) delegate pure detection,\n * stripping, and mode-section extraction to this module while\n * keeping host-specific concerns (lazy file loading, session\n * state, command registration) locally.\n *\n * @module @maestria/shared-mode\n */\n\n// ── Constants ──\n\nexport const MODE_KEYWORDS = ['fein', 'sonar', 'blitz'] as const;\nexport type ModeKeyword = (typeof MODE_KEYWORDS)[number];\n\n/**\n * Backward-compatible alias used by OpenCode (`VALID_KEYWORDS`).\n */\nexport const VALID_KEYWORDS: readonly ModeKeyword[] = MODE_KEYWORDS;\n\nexport const MODE_MARKERS: Record<ModeKeyword, string> = {\n blitz: '[MODE: blitz]',\n fein: '[MODE: fein]',\n sonar: '[MODE: sonar]',\n};\n\n/**\n * Priority mapping for mode keyword restrictiveness.\n * Higher number = more restrictive = wins when multiple keywords are present.\n * fein (3): full pipeline with mandatory gates\n * sonar (2): research only, no code\n * blitz (1): fast implementation, skip optional ceremony; required review remains\n */\nexport const MODE_PRIORITY: Record<ModeKeyword, number> = {\n blitz: 1,\n fein: 3,\n sonar: 2,\n};\n\n// ── Types ──\n\n/**\n * Result of pure mode detection (no prompt/marker).\n * Host wrappers add prompt/marker and stripped text as needed.\n */\nexport interface ModeDetectPure {\n mode: ModeKeyword;\n keyword: string;\n index: number;\n}\n\n// ── Pure helpers ──\n\n/**\n * Regex matching fenced code blocks (```) and inline backtick spans (`).\n * Unclosed fenced blocks (``` without closing ```) are intentionally not\n * excluded - the regex requires matching fences. This is the accepted\n * false-positive behavior documented in ADR-OC-003.\n */\nexport const CODE_BLOCK_RE = /```[\\s\\S]*?```|`[^`]*`/gu;\n\nexport const findCodeBlockRanges = (text: string): [number, number][] => {\n const ranges: [number, number][] = [];\n let match: RegExpExecArray | null;\n // Reset lastIndex in case callers reuse the exported regex\n CODE_BLOCK_RE.lastIndex = 0;\n while ((match = CODE_BLOCK_RE.exec(text)) !== null) {\n ranges.push([match.index, match.index + match[0].length]);\n }\n return ranges;\n};\n\nexport const isInRanges = (index: number, ranges: [number, number][]): boolean =>\n ranges.some(([start, end]) => index >= start && index < end);\n\n/**\n * Extract the `## MODE:` section from a command file's content.\n * Mirrors the logic in both original modules: find `## MODE:`, slice\n * from there, trim trailing whitespace, append a single newline; if\n * no marker is found, normalize trailing whitespace the same way.\n */\nexport const extractModeSection = (content: string): string => {\n const modeIdx = content.indexOf('## MODE:');\n if (modeIdx !== -1) {\n return `${content.slice(modeIdx).replace(/\\s+$/u, '')}\\n`;\n }\n return `${content.replace(/\\s+$/u, '')}\\n`;\n};\n\nconst escapeRegExp = (value: string): string => value.replaceAll(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n\nconst buildKeywordRegex = (keyword: string): RegExp =>\n new RegExp(`\\\\b${escapeRegExp(keyword)}\\\\b`, 'giu');\n\nconst isModeKeyword = (value: string): value is ModeKeyword =>\n (MODE_KEYWORDS as readonly string[]).includes(value);\n\nexport const getModeMarker = (mode: string): string => {\n if (isModeKeyword(mode)) {\n return MODE_MARKERS[mode];\n }\n return '';\n};\n\n// ── Detection ──\n\n/**\n * Detect a workflow mode keyword in the given text.\n *\n * Rules (per ADR-OC-003):\n * - Word-boundary regex matching (`\\bfein\\b`, etc.), case-insensitive\n * - Most restrictive match wins (fein > sonar > blitz) regardless of position\n * - Disabled keywords (case-insensitive) are ignored\n * - Matches inside fenced code blocks (```) and inline backticks (`) are ignored\n * - Unclosed fences are not excluded (accepted false-positive)\n *\n * Returns the pure detection result (mode, matched keyword text, index) or null.\n */\nexport const detectMode = (text: string, disabled?: Set<string>): ModeDetectPure | null => {\n if (!text) {\n return null;\n }\n const codeRanges = findCodeBlockRanges(text);\n const normalizedDisabled = disabled\n ? new Set([...disabled].map((k) => k.toLowerCase()))\n : undefined;\n let best: ModeDetectPure | null = null;\n\n for (const keyword of MODE_KEYWORDS) {\n if (\n normalizedDisabled !== undefined &&\n normalizedDisabled !== null &&\n normalizedDisabled.has(keyword)\n ) {\n continue;\n }\n const regex = buildKeywordRegex(keyword);\n let match: RegExpExecArray | null;\n while ((match = regex.exec(text)) !== null) {\n if (isInRanges(match.index, codeRanges)) {\n continue;\n }\n if (best === null || MODE_PRIORITY[keyword] > MODE_PRIORITY[best.mode]) {\n best = { index: match.index, keyword: match[0], mode: keyword };\n }\n }\n }\n\n return best;\n};\n\n/**\n * Remove the matched keyword from the text, cleaning up a trailing colon\n * and collapsing double spaces.\n *\n * Mirrors both originals: strip a leading colon and surrounding whitespace\n * after the keyword, then collapse double spaces and trim.\n */\nexport const stripKeyword = (text: string, result: { index: number; keyword: string }): string => {\n const before = text.slice(0, result.index);\n const after = text.slice(result.index + result.keyword.length);\n const cleaned = after.replace(/^:\\s*/u, '');\n return (before + cleaned).replaceAll(/ {2,}/gu, ' ').trim();\n};\n","import path from 'node:path';\n\nconst __dirname = import.meta.dirname;\nexport const PACKAGE_ROOT = path.resolve(__dirname, '..');\nexport const AGENTS_DIR = path.join(PACKAGE_ROOT, 'agents');\nexport const COMMANDS_DIR = path.join(PACKAGE_ROOT, 'agents', 'commands');\nexport const RULES_PATH = path.join(PACKAGE_ROOT, 'rules', 'AGENTS.md');\n","import { extractModeSection } from '@maestria/shared-mode';\nimport { readFileSync } from 'node:fs';\nimport path from 'node:path';\n\nimport type { ModeKeyword } from '@/modes/types.js';\nimport { COMMANDS_DIR } from '@/root.js';\n\nconst VALID_KEYWORDS: readonly ModeKeyword[] = ['fein', 'sonar', 'blitz'];\n\nconst loadModePrompt = (name: string): string => {\n const content = readFileSync(path.resolve(COMMANDS_DIR, `${name}.md`), 'utf-8');\n return extractModeSection(content);\n};\n\nconst isModeKeyword = (value: string): value is ModeKeyword =>\n VALID_KEYWORDS.some((keyword) => keyword === value);\n\nconst modePromptTarget: Record<string, string> = {};\n\n/**\n * Mode prompt text for each keyword, lazily loaded on first access.\n * If a prompt file is missing or unreadable, logs a warning and caches\n * an empty string - never throws at module evaluation time.\n *\n * @see ADR-OC-003 (section \"Mode Prompts\")\n */\nexport const MODE_PROMPTS: Record<ModeKeyword, string> = new Proxy(modePromptTarget, {\n get(target, key, receiver) {\n if (typeof key === 'string' && isModeKeyword(key)) {\n if (!(key in target)) {\n try {\n target[key] = loadModePrompt(key);\n } catch (error) {\n console.warn(`[maestria] Failed to load mode prompt \"${key}\":`, error);\n target[key] = '';\n }\n }\n return target[key];\n }\n return Reflect.get(target, key, receiver) as unknown;\n },\n});\n\n/**\n * Marker strings for each mode keyword, used to signal the active mode.\n * Format: `[MODE: <keyword>]`\n */\nexport const MODE_MARKERS: Record<ModeKeyword, string> = {\n blitz: '[MODE: blitz]',\n fein: '[MODE: fein]',\n sonar: '[MODE: sonar]',\n};\n\n/**\n * Array of all valid mode keywords for runtime iteration.\n */\nexport { VALID_KEYWORDS };\n","import {\n detectMode as sharedDetectMode,\n getModeMarker as sharedGetMarker,\n stripKeyword as sharedStripKeyword,\n} from '@maestria/shared-mode';\n\nimport { MODE_MARKERS, MODE_PROMPTS, VALID_KEYWORDS } from '@/modes/prompts.js';\nimport type { ModeKeyword, ModeResult } from '@/modes/types.js';\n\n/**\n * Detect a workflow mode keyword in the given text.\n *\n * Delegates pure detection (word-boundary, priority, code-block\n * exclusion, disabled-keyword handling, case-insensitivity) to\n * `@maestria/shared-mode` and augments with prompt/marker so the\n * existing public API and result shape are preserved.\n *\n * Behavior (per ADR-OC-003) is unchanged: most restrictive wins\n * (fein > sonar > blitz), code spans are excluded, unclosed fences\n * are not excluded (accepted false-positive).\n */\nconst isModeKeyword = (value: string): value is ModeKeyword =>\n (VALID_KEYWORDS as readonly string[]).includes(value);\n\nexport const detectMode = (text: string, disabled?: Set<string>): ModeResult | null => {\n const pure = sharedDetectMode(text, disabled);\n if (pure === null) {\n return null;\n }\n return {\n index: pure.index,\n keyword: pure.keyword,\n marker: MODE_MARKERS[pure.mode],\n mode: pure.mode,\n prompt: MODE_PROMPTS[pure.mode],\n };\n};\n\n/**\n * Remove the matched keyword from the text, cleaning up any trailing colon\n * or whitespace that may follow it.\n */\nexport const stripKeyword = (text: string, result: ModeResult): string =>\n sharedStripKeyword(text, result);\n\n/**\n * Get the mode prompt text for a given mode name.\n */\nexport const getModePrompt = (mode: string): string => {\n if (isModeKeyword(mode)) {\n return MODE_PROMPTS[mode];\n }\n return '';\n};\n\n/**\n * Get the mode marker string for a given mode name.\n */\nexport const getModeMarker = (mode: string): string => {\n if (isModeKeyword(mode)) {\n return sharedGetMarker(mode);\n }\n return '';\n};\n","/**\n * Types for keyword-triggered workflow modes.\n *\n * @see ADR-OC-003 for full design context.\n */\n\nimport { z } from 'zod';\n\n/**\n * Valid mode keywords.\n *\n * - `\"fein\"` -- Full pipeline (recon -> design -> build -> review)\n * - `\"sonar\"` -- Research only (recon + design, stop before build)\n * - `\"blitz\"` -- Fast implementation (builder direct, skip optional recon/design; required review remains)\n */\nexport const modeKeywordSchema = z.enum(['fein', 'sonar', 'blitz']);\nexport type ModeKeyword = z.infer<typeof modeKeywordSchema>;\n\n/**\n * Plugin-level options for @maestria/opencode.\n */\nexport const maestriaOptionsSchema = z.object({\n modes: z\n .object({\n disabledKeywords: z.array(modeKeywordSchema).optional(),\n })\n .optional(),\n});\nexport type MaestriaPluginOptions = z.infer<typeof maestriaOptionsSchema>;\n\n/**\n * Result returned when a mode keyword is detected in a message.\n */\nexport interface ModeResult {\n /** The resolved mode keyword (lowercase). */\n mode: ModeKeyword;\n /** The keyword string as matched in the original text. */\n keyword: string;\n /** The character index where the keyword starts in the original text. */\n index: number;\n /** The mode prompt text to inject. */\n prompt: string;\n /** The mode marker string like `[MODE: fein]`. */\n marker: string;\n}\n","import type { Config, Hooks, Plugin, PluginInput } from '@opencode-ai/plugin';\nimport { merge } from 'es-toolkit';\nimport { readdirSync, readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\n\nimport { detectMode, getModeMarker, getModePrompt, stripKeyword } from '@/modes/index.js';\nimport { maestriaOptionsSchema } from '@/modes/types.js';\nimport type { MaestriaPluginOptions } from '@/modes/types.js';\nimport { AGENTS_DIR, RULES_PATH } from '@/root.js';\n\ntype OpenCodeAgentConfig = NonNullable<NonNullable<Config['agent']>[string]>;\ntype AgentMode = NonNullable<OpenCodeAgentConfig['mode']>;\ntype AgentPermission = NonNullable<OpenCodeAgentConfig['permission']> & Record<string, unknown>;\ntype AgentConfig = Omit<OpenCodeAgentConfig, 'mode' | 'permission'> & {\n mode: AgentMode;\n permission: AgentPermission;\n};\ntype ChatMessageHook = NonNullable<Hooks['chat.message']>;\ntype ChatMessageInput = Parameters<ChatMessageHook>[0];\ntype ChatMessageOutput = Parameters<ChatMessageHook>[1];\ntype TextPart = Extract<ChatMessageOutput['parts'][number], { type: 'text' }>;\ntype ConfigInput = Parameters<NonNullable<Hooks['config']>>[0];\ntype CompactingOutput = Parameters<NonNullable<Hooks['experimental.session.compacting']>>[1];\n\ninterface AgentFrontmatter {\n description: string;\n mode: AgentMode;\n permission: AgentPermission;\n color?: string;\n maxSteps?: number;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isAgentMode = (value: unknown): value is AgentMode =>\n value === 'all' || value === 'primary' || value === 'subagent';\n\nconst parsePermission = (value: unknown): AgentPermission => {\n if (!isRecord(value)) {\n return {};\n }\n\n const permission: AgentPermission = {};\n for (const [key, permissionValue] of Object.entries(value)) {\n permission[key] = permissionValue;\n }\n return permission;\n};\n\nconst parseFrontmatter = (yamlStr: string): AgentFrontmatter => {\n const parsed = parseYaml(yamlStr) as unknown;\n const result = isRecord(parsed) ? parsed : {};\n\n return {\n color: typeof result.color === 'string' ? result.color : undefined,\n description: typeof result.description === 'string' ? result.description : '',\n maxSteps:\n result.maxSteps !== undefined && result.maxSteps !== null && result.maxSteps !== ''\n ? Number(result.maxSteps)\n : undefined,\n mode: isAgentMode(result.mode) ? result.mode : 'subagent',\n permission: parsePermission(result.permission),\n };\n};\n\n/**\n * Read an agent markdown file and split into frontmatter + prompt.\n */\nconst parseAgentFile = (filePath: string): { name: string; config: AgentConfig } => {\n const content = readFileSync(filePath, 'utf-8');\n const name = path.basename(filePath, '.md');\n\n const parts = content.split('---');\n if (parts.length < 3) {\n throw new Error(`Invalid agent file: ${filePath} - missing frontmatter`);\n }\n\n const frontmatter = parseFrontmatter(parts[1].trim());\n const prompt = parts.slice(2).join('---').trim();\n\n const config: AgentConfig = {\n description: frontmatter.description,\n mode: frontmatter.mode,\n permission: frontmatter.permission,\n prompt,\n };\n\n if (frontmatter.color !== undefined && frontmatter.color !== null && frontmatter.color !== '') {\n config.color = frontmatter.color;\n }\n if (\n frontmatter.maxSteps !== undefined &&\n frontmatter.maxSteps !== null &&\n frontmatter.maxSteps !== 0\n ) {\n config.maxSteps = frontmatter.maxSteps;\n }\n\n return { config, name };\n};\n\n/**\n * Load all agent configs from the bundled agents/ directory.\n * Returns partial results if some agent files fail to load.\n */\nconst loadAgents = (): NonNullable<Config['agent']> => {\n try {\n const files = readdirSync(AGENTS_DIR).filter((file) => file.endsWith('.md'));\n const agents: NonNullable<Config['agent']> = {};\n\n for (const file of files) {\n try {\n const { name, config } = parseAgentFile(path.join(AGENTS_DIR, file));\n agents[name] = config;\n } catch (error) {\n console.warn(`[maestria] Failed to parse agent file \"${file}\":`, error);\n }\n }\n\n return agents;\n } catch (error) {\n console.error('[maestria] Failed to read agents directory:', error);\n throw new Error(\n `[maestria] Failed to load agents from \"${AGENTS_DIR}\": ${\n error instanceof Error ? error.message : String(error)\n }`,\n { cause: error },\n );\n }\n};\n\nconst applyModeToMessage = (\n hookInput: ChatMessageInput,\n hookOutput: ChatMessageOutput,\n disabledKeywords: Set<string>,\n): void => {\n if (hookInput.agent !== 'orchestrator') {\n return;\n }\n\n const textPart = hookOutput.parts.find((part): part is TextPart => part.type === 'text');\n if (textPart === undefined) {\n return;\n }\n\n const result = detectMode(textPart.text, disabledKeywords);\n if (result === null) {\n return;\n }\n\n textPart.text = [\n getModeMarker(result.mode),\n '',\n getModePrompt(result.mode),\n '',\n stripKeyword(textPart.text, result),\n ].join('\\n');\n};\n\nconst configureAgents = (input: ConfigInput, agents: NonNullable<Config['agent']>): void => {\n input.agent = merge(input.agent ?? {}, agents);\n input.instructions = [...(input.instructions ?? []), RULES_PATH];\n};\n\nconst appendCompactionContext = (output: CompactingOutput): void => {\n output.context.push(\n 'Session was compacted. Task tracking is maintained via todowrite. ' +\n 'Active context (files, decisions, blockers) was captured before compaction. ' +\n 'Continue where you left off.',\n );\n};\n\nexport const MaestriaPlugin: Plugin = async (\n _input: PluginInput,\n options?: MaestriaPluginOptions,\n) => {\n const parsed = maestriaOptionsSchema.parse(options ?? {});\n const disabledKeywords = new Set<string>(\n (parsed.modes?.disabledKeywords ?? []).map((keyword) => keyword.toLowerCase()),\n );\n const agents = loadAgents();\n await Promise.resolve();\n\n return {\n 'chat.message': async (hookInput, hookOutput) => {\n applyModeToMessage(hookInput, hookOutput, disabledKeywords);\n await Promise.resolve();\n },\n config: async (input) => {\n configureAgents(input, agents);\n await Promise.resolve();\n },\n 'experimental.session.compacting': async (_compactingInput, output) => {\n appendCompactionContext(output);\n await Promise.resolve();\n },\n };\n};\n\nexport default MaestriaPlugin;\n"],"mappings":"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"}
|