| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 |
- 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, input.sessionID)
- }
- },
- // 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 }
|