From db90b64f54a730c53d66966551acecfd2a5d94f7 Mon Sep 17 00:00:00 2001 From: Markov Date: Tue, 24 Feb 2026 22:04:29 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20bootstrap=20context=20=D0=B8=D0=B7=20AG?= =?UTF-8?q?ENT.md=20=D0=B8=20memory/=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - loadBootstrapContext() загружает AGENT.md, memory/notes.md, memory/projects/*.md - Лимит 15K символов, автотрункейт - agentHome передаётся из router в agent options - System prompt из agent.json сохранён как fallback --- src/agent.ts | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/router.ts | 3 ++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/agent.ts b/src/agent.ts index 079d719..e1ad2d3 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -29,6 +29,8 @@ export interface AgentOptions { allowedPaths?: string[]; /** Additional custom tools (e.g. tracker tools) */ customTools?: ToolDefinition[]; + /** Agent home directory for loading bootstrap files (AGENT.md, memory/) */ + agentHome?: string; } export interface AgentMessage { @@ -37,6 +39,53 @@ export interface AgentMessage { sessionId?: string; } +// --- Bootstrap context loader --- +const BOOTSTRAP_FILES = ['AGENT.md', 'memory/notes.md']; +const BOOTSTRAP_MAX_CHARS = 15_000; + +function loadBootstrapContext(agentHome: string): string { + const parts: string[] = []; + let totalChars = 0; + + for (const relPath of BOOTSTRAP_FILES) { + const filePath = path.join(agentHome, relPath); + try { + if (!fs.existsSync(filePath)) continue; + const content = fs.readFileSync(filePath, 'utf-8').trim(); + if (!content) continue; + const remaining = BOOTSTRAP_MAX_CHARS - totalChars; + if (remaining <= 0) break; + const truncated = content.length > remaining ? content.slice(0, remaining) + '\n[...truncated]' : content; + parts.push(`## ${relPath}\n${truncated}`); + totalChars += truncated.length; + } catch { + // skip unreadable files + } + } + + // Also load per-project memory files + const projectMemDir = path.join(agentHome, 'memory', 'projects'); + try { + if (fs.existsSync(projectMemDir)) { + for (const file of fs.readdirSync(projectMemDir).filter(f => f.endsWith('.md'))) { + const filePath = path.join(projectMemDir, file); + const content = fs.readFileSync(filePath, 'utf-8').trim(); + if (!content) continue; + const remaining = BOOTSTRAP_MAX_CHARS - totalChars; + if (remaining <= 200) break; + const truncated = content.length > remaining ? content.slice(0, remaining) + '\n[...truncated]' : content; + parts.push(`## memory/projects/${file}\n${truncated}`); + totalChars += truncated.length; + } + } + } catch { + // skip + } + + if (parts.length === 0) return ''; + return `# Agent Context (bootstrap)\n\n${parts.join('\n\n')}`; +} + // --- Model alias map: short name → full model ID --- const MODEL_ALIASES: Record = { 'sonnet': 'claude-sonnet-4-6', @@ -185,6 +234,15 @@ export async function* runAgent( // Build system prompt additions const promptParts: string[] = []; + // Load bootstrap context from agent home (AGENT.md, memory/) + if (options.agentHome) { + const bootstrapContext = loadBootstrapContext(options.agentHome); + if (bootstrapContext) { + promptParts.push(bootstrapContext); + log.info({ chars: bootstrapContext.length }, 'Bootstrap context loaded'); + } + } + if (skillsXml) { promptParts.push( '## Skills (mandatory)', diff --git a/src/router.ts b/src/router.ts index 75f0a0a..a13d22f 100644 --- a/src/router.ts +++ b/src/router.ts @@ -100,13 +100,14 @@ export class EventRouter { sessionId: this.config.sessionId, model: this.config.model, provider: this.config.provider, - systemPrompt: this.config.prompt || undefined, + systemPrompt: this.config.prompt || undefined, // fallback if no AGENT.md skillsDir: this.config.agentHome, sessionDir: path.join(this.config.agentHome, 'sessions'), allowedPaths: this.config.allowedPaths.length > 0 ? this.config.allowedPaths : [this.config.agentHome], customTools: this.trackerTools, + agentHome: this.config.agentHome, })) { if (msg.type === 'error') { this.log.error({ error: msg.content }, 'Agent error');