feat: bootstrap context из AGENT.md и memory/ файлов
- loadBootstrapContext() загружает AGENT.md, memory/notes.md, memory/projects/*.md - Лимит 15K символов, автотрункейт - agentHome передаётся из router в agent options - System prompt из agent.json сохранён как fallback
This commit is contained in:
parent
dea2bcaef6
commit
db90b64f54
58
src/agent.ts
58
src/agent.ts
@ -29,6 +29,8 @@ export interface AgentOptions {
|
|||||||
allowedPaths?: string[];
|
allowedPaths?: string[];
|
||||||
/** Additional custom tools (e.g. tracker tools) */
|
/** Additional custom tools (e.g. tracker tools) */
|
||||||
customTools?: ToolDefinition[];
|
customTools?: ToolDefinition[];
|
||||||
|
/** Agent home directory for loading bootstrap files (AGENT.md, memory/) */
|
||||||
|
agentHome?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentMessage {
|
export interface AgentMessage {
|
||||||
@ -37,6 +39,53 @@ export interface AgentMessage {
|
|||||||
sessionId?: string;
|
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 ---
|
// --- Model alias map: short name → full model ID ---
|
||||||
const MODEL_ALIASES: Record<string, string> = {
|
const MODEL_ALIASES: Record<string, string> = {
|
||||||
'sonnet': 'claude-sonnet-4-6',
|
'sonnet': 'claude-sonnet-4-6',
|
||||||
@ -185,6 +234,15 @@ export async function* runAgent(
|
|||||||
// Build system prompt additions
|
// Build system prompt additions
|
||||||
const promptParts: string[] = [];
|
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) {
|
if (skillsXml) {
|
||||||
promptParts.push(
|
promptParts.push(
|
||||||
'## Skills (mandatory)',
|
'## Skills (mandatory)',
|
||||||
|
|||||||
@ -100,13 +100,14 @@ export class EventRouter {
|
|||||||
sessionId: this.config.sessionId,
|
sessionId: this.config.sessionId,
|
||||||
model: this.config.model,
|
model: this.config.model,
|
||||||
provider: this.config.provider,
|
provider: this.config.provider,
|
||||||
systemPrompt: this.config.prompt || undefined,
|
systemPrompt: this.config.prompt || undefined, // fallback if no AGENT.md
|
||||||
skillsDir: this.config.agentHome,
|
skillsDir: this.config.agentHome,
|
||||||
sessionDir: path.join(this.config.agentHome, 'sessions'),
|
sessionDir: path.join(this.config.agentHome, 'sessions'),
|
||||||
allowedPaths: this.config.allowedPaths.length > 0
|
allowedPaths: this.config.allowedPaths.length > 0
|
||||||
? this.config.allowedPaths
|
? this.config.allowedPaths
|
||||||
: [this.config.agentHome],
|
: [this.config.agentHome],
|
||||||
customTools: this.trackerTools,
|
customTools: this.trackerTools,
|
||||||
|
agentHome: this.config.agentHome,
|
||||||
})) {
|
})) {
|
||||||
if (msg.type === 'error') {
|
if (msg.type === 'error') {
|
||||||
this.log.error({ error: msg.content }, 'Agent error');
|
this.log.error({ error: msg.content }, 'Agent error');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user