import type { Plugin, Hooks } from "@kirincode-ai/plugin" import { tool } from "@kirincode-ai/plugin" import { MemoryStore } from "./memory" import { SkillRegistry } from "./skills" import { SubAgentRegistry } from "./subagent" import { handleMemoryCommand, handleSkillCommand, handleSubAgentCommand } from "./commands" export const KirinCodePlugin: Plugin = async ({ client, project, directory, worktree, $ }) => { const store = new MemoryStore(worktree) const registry = new SkillRegistry() const subagents = new SubAgentRegistry() // Load skills at startup registry.load(worktree) // Load memory summary for context injection const memorySummary = store.loadSummary() return { // Inject memory + skills + sub-agents into system context "experimental.chat.system.transform": async (_input, output) => { const extras: string[] = [] if (memorySummary) { extras.push("## Your Memory (from past sessions)", memorySummary) } const skillCtx = registry.formatSystemContext() if (skillCtx) { extras.push(skillCtx) } const subAgentCtx = subagents.formatSystemContext() if (subAgentCtx) { extras.push(subAgentCtx) } if (extras.length) { output.system = [...output.system, ...extras] } }, // Handle /memory, /skill, and /subagent commands "command.execute.before": async (input, output) => { if (input.command === "memory") { output.parts = await handleMemoryCommand(input.arguments, store, input.sessionID) } if (input.command === "skill") { output.parts = await handleSkillCommand(input.arguments, registry, worktree, input.sessionID) } if (input.command === "subagent") { output.parts = await handleSubAgentCommand(input.arguments, subagents) } }, // Auto-memory on session idle event: async ({ event }) => { if (event.type === "session.idle") { const today = new Date().toISOString().slice(0, 10) store.updateIndex({ date: today, category: "session", summary: `Session completed in ${directory}`, tags: ["auto"], }) } }, // Preserve memory context across compaction "experimental.session.compacting": async (_input, output) => { const summary = store.loadSummary() if (summary) { output.context.push("## Persisted Memory\n" + summary) } }, // Shell env: expose KIRINCODE_ env vars "shell.env": async (_input, output) => { output.env.KIRINCODE_MEMORY_DIR = store.memoryDir output.env.KIRINCODE_SUBAGENTS = String(subagents.list().length) }, // Sub-agent tools for multi-agent task delegation tool: { subagent_spawn: tool({ description: "Spawn a sub-agent to work on a specific task in the background. Multiple sub-agents can run concurrently to parallelize work. Use subagent_list to check status and subagent_collect to retrieve results when done.", args: { name: tool.schema.string().describe("Unique name for this sub-agent"), task: tool.schema.string().describe("The task or prompt to send to the sub-agent"), agent: tool.schema.string().optional().describe("Agent type: build, plan, general, or explore. Defaults to general."), model: tool.schema.string().optional().describe("Model override, e.g. 'anthropic/claude-sonnet-4-5'"), }, async execute(args, context) { const def = subagents.spawn(client, context.sessionID, { name: args.name, task: args.task, agent: args.agent, model: args.model, }) return `Sub-agent '${def.name}' spawned (${def.id}). Use subagent_collect '${def.name}' to retrieve results when ready.` }, }), subagent_list: tool({ description: "List all spawned sub-agents and their current status. Use after spawning to monitor progress.", args: {}, async execute() { const list = subagents.list() if (list.length === 0) { return "No sub-agents spawned. Use subagent_spawn to create one." } const lines = ["## Sub-Agents", ""] for (const a of list) { const icon = a.status === "completed" ? "✅" : a.status === "failed" ? "❌" : a.status === "spawning" ? "⏳" : "🔄" const taskPreview = a.task.slice(0, 60) lines.push(`- ${icon} **${a.name}** — ${a.status} — ${taskPreview}${a.task.length > 60 ? "..." : ""}`) } return lines.join("\n") }, }), subagent_status: tool({ description: "Check the detailed status of a specific sub-agent by name, including elapsed time and any errors.", args: { name: tool.schema.string().describe("Name of the sub-agent to check"), }, async execute(args) { return subagents.status(args.name) }, }), subagent_collect: tool({ description: "Wait for a sub-agent to finish and collect its results. Blocks until the sub-agent completes or fails. Use after subagent_list shows a sub-agent is running.", args: { name: tool.schema.string().describe("Name of the sub-agent to collect results from"), }, async execute(args) { return await subagents.collect(args.name) }, }), }, } satisfies Hooks } export { MemoryStore, SkillRegistry, SubAgentRegistry }