UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

163 lines (162 loc) • 9.59 kB
import { PROJECT_CONTEXT_FRAMING } from "../../prompts/default.js"; import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js"; import { composeMemoryBlock } from "../memory.js"; import { hasConversationContent } from "../session.js"; import { createSharedMemoryTools } from "../shared-memory/tools.js"; import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js"; import { defineTool } from "../tools.js"; import { readFrozenSkillGrants, seedSkillGrants, skillSeatReader } from "../skill-grant.js"; import { allowedToolsNotGrantedNotice, seedAllowEntryTables } from "../allow-entry.js"; import { deliverEngineNotice } from "../engine-notice.js"; import { announceMemoryNotMounted } from "./announce-once-ledger.js"; import { SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createSkillTool, normalizeSkills } from "./synthetic-tools.js"; import { skillLoadReading } from "./skill-base-dir.js"; import { RosterBuilder } from "../tool-roster.js"; import { mountedToolNames } from "../tool-name-aliases.js"; import { valveLeaves } from "../../agents/tool-filter.js"; import { asThrownError } from "../thrown-value.js"; export async function prepareProjectContext(input) { const { deps, spec, internals, session, sessionId, runId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillGrants, allowedTools, allowedToolTables, roster, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume, skillListingBudget } = input; const tools = roster.tools; const leaves = valveLeaves(spec, toolFaceSnapshot.selector); let memoryBlock = memoryBlockFromEngine; let instructionSources; let projectInstructionContent; if (deps.loadProjectMemory) { let projectMemoryPhase = spec.sessionId ? "resume" : "fresh"; if (spec.sessionId) { try { const branch = await session.getBranch(); const hasConversation = hasConversationContent(branch); if (!hasConversation) projectMemoryPhase = "fresh"; else { for (let i = branch.length - 1; i >= 0; i--) { const e = branch[i]; if (e.type === "compaction") { projectMemoryPhase = "post-compact"; break; } if (e.type === "message" && e.message.role === "user") break; } } } catch { } } let loaded = null; try { loaded = await Promise.resolve(deps.loadProjectMemory({ cwd: taskRootFinal, handsEnabled, isSubagent: delegation.isNonForkChild, ...(internals?.agentName ? { agentName: internals.agentName } : {}), sessionId, runId, phase: projectMemoryPhase, })); } catch (err) { deps.onError?.(asThrownError(err), { phase: "config", sessionId }); } const projectMem = loaded !== null && typeof loaded === "object" ? loaded.content : loaded; const seededFiles = loaded !== null && typeof loaded === "object" ? loaded.seededFiles : undefined; const declaredSources = loaded !== null && typeof loaded === "object" ? loaded.instructionSources : undefined; if (declaredSources !== undefined && declaredSources.length > 0) instructionSources = declaredSources; if (seededFiles?.length && seedContextFiles) { try { await seedContextFiles(seededFiles); } catch (err) { deps.onError?.(asThrownError(err), { phase: "config", sessionId }); } } if (projectMem != null && projectMem.trim()) { projectInstructionContent = projectMem; const projectBlock = `${PROJECT_CONTEXT_FRAMING}\n\n${composeMemoryBlock(projectMem, "project")}`; memoryBlock = memoryBlock ? `${memoryBlock}\n\n${projectBlock}` : projectBlock; } } const declaredSkillRank = new Map(); for (const s of spec.skills ?? []) if (!declaredSkillRank.has(s.name)) declaredSkillRank.set(s.name, declaredSkillRank.size); const skillSpecs = normalizeSkills(spec.skills ?? []).filter((s) => { const { loads, delivered } = skillLoadReading(s, SKILL_CONTENT_MAX_CHARS); if (loads) return true; deps.onError?.(new Error(`Skill "${s.name}" content is ${delivered} chars${delivered === s.content.length ? "" : ` once its base directory resolves (${s.content.length} as written)`}, over the ${SKILL_CONTENT_MAX_CHARS}-char load limit — skill not loaded (skills are never truncated; shrink the body or move material to attachments/files).`), { phase: "config", sessionId }); return false; }); const frozenSkillGrants = readFrozenSkillGrants(resume?.seed.skillGrants); if (frozenSkillGrants === undefined) throw new Error("the resumed checkpoint's skill-grant section does not read as grant rows — refusing to seed the run without the refusals it carries (the resume screen refuses this row before the claim)"); const mounted = () => mountedToolNames(tools); skillGrants.bindMountedNames(mounted); skillGrants.bindSkills(skillSpecs, SKILL_TOOL_NAME); const inert = seedAllowEntryTables(allowedToolTables, { skillInert: seedSkillGrants(skillGrants, { skills: skillSpecs, userInvokedSkills: spec.userInvokedSkills, inherited: [...(internals?.inheritedSkillGrants ?? []), ...frozenSkillGrants] }), inherited: internals?.inheritedAllowedTools, request: allowedTools, deployment: deps.allowedTools }, mounted()); if (inert.length > 0) deliverEngineNotice(deps.onNotice, allowedToolsNotGrantedNotice(inert, sessionId)); if (skillSpecs.length > 0) { if (tools.some((t) => t.name === SKILL_TOOL_NAME)) { const e = new Error(`Tool name "${SKILL_TOOL_NAME}" is reserved when spec.skills is present.`); e.code = "config.reserved_tool_name"; throw e; } roster.mount(createSkillTool(skillSpecs, skillGrants), { source: "builtin", mountedBy: "optional" }); } const skillSeatOf = skillSeatReader(skillSpecs, mounted); const sharedMemoryProvider = deps.sharedMemoryStores; let sharedMemoryPairMounted = false; if (sharedMemoryProvider !== undefined) { const mountedNameDomain = new Set(); for (const t of tools) { mountedNameDomain.add(t.name); for (const alias of t.aliases ?? []) mountedNameDomain.add(alias); } const excludedName = SHARED_MEMORY_TOOL_NAMES.find((n) => !leaves({ name: n })); const occupiedName = SHARED_MEMORY_TOOL_NAMES.find((n) => mountedNameDomain.has(n)); if (excludedName !== undefined || occupiedName !== undefined) { announceMemoryNotMounted(onceLedger, sessionId, "shared", SHARED_MEMORY_TOOL_NAMES, excludedName !== undefined ? `the run's tool face (allowTools / excludeTools / excludeAllTools) does not admit "${excludedName}"` : `this task already declares a tool named "${occupiedName}" (the built-in yields to it)`); } else { roster.mountAll(createSharedMemoryTools({ provider: sharedMemoryProvider, context: { sessionId, taskId: hostTaskId, ...(spec.principal !== undefined ? { principal: spec.principal } : {}) }, }).map((s) => defineTool(s)), { source: "builtin", mountedBy: "memory" }); sharedMemoryPairMounted = true; } } let memoryEnginePairMounted = false; if (memoryTools !== undefined && memoryTools.length > 0) { roster.mountAll(memoryTools.map((s) => defineTool(s)), { source: "builtin", mountedBy: "memory" }); memoryEnginePairMounted = true; } else if (memoryEngineSession !== undefined && !memorySearchToolsPlanned) { announceMemoryNotMounted(onceLedger, sessionId, "engine", MEMORY_ENGINE_TOOL_NAMES, memoryPairExcludedName !== undefined ? `the run's tool face (allowTools / excludeTools / excludeAllTools) does not admit "${memoryPairExcludedName}"` : `this task already declares a tool named "${memoryPairOccupiedName}" (the built-in yields to it)`); } const skillsListing = skillSpecs.length > 0 && leaves({ name: SKILL_TOOL_NAME }) ? { entries: skillSpecs.map((s) => ({ name: s.name, description: s.description, ...(s.files !== undefined ? { files: s.files.map((f) => ({ path: f.path })) } : {}), ...(declaredSkillRank.get(s.name) !== undefined ? { declaredRank: declaredSkillRank.get(s.name) } : {}), })), seedAnnounced: resume !== undefined || spec.sessionId !== undefined, budget: skillListingBudget, } : (resume?.seed.announcedListings?.skills?.length ?? 0) > 0 || spec.sessionId !== undefined ? { entries: [], seedAnnounced: true, budget: skillListingBudget } : undefined; return { memoryBlock, instructionSources, projectInstructionContent, skillSpecs, skillSeatOf, skillsListing, sharedMemoryPairMounted, memoryEnginePairMounted }; }