index.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import type { Plugin, Hooks } from "@kirincode-ai/plugin"
  2. import { tool } from "@kirincode-ai/plugin"
  3. import { MemoryStore } from "./memory"
  4. import { SkillRegistry } from "./skills"
  5. import { SubAgentRegistry } from "./subagent"
  6. import { handleMemoryCommand, handleSkillCommand, handleSubAgentCommand } from "./commands"
  7. export const KirinCodePlugin: Plugin = async ({ client, project, directory, worktree, $ }) => {
  8. const store = new MemoryStore(worktree)
  9. const registry = new SkillRegistry()
  10. const subagents = new SubAgentRegistry()
  11. // Load skills at startup
  12. registry.load(worktree)
  13. // Load memory summary for context injection
  14. const memorySummary = store.loadSummary()
  15. return {
  16. // Inject memory + skills + sub-agents into system context
  17. "experimental.chat.system.transform": async (_input, output) => {
  18. const extras: string[] = []
  19. if (memorySummary) {
  20. extras.push("## Your Memory (from past sessions)", memorySummary)
  21. }
  22. const skillCtx = registry.formatSystemContext()
  23. if (skillCtx) {
  24. extras.push(skillCtx)
  25. }
  26. const subAgentCtx = subagents.formatSystemContext()
  27. if (subAgentCtx) {
  28. extras.push(subAgentCtx)
  29. }
  30. if (extras.length) {
  31. output.system = [...output.system, ...extras]
  32. }
  33. },
  34. // Handle /memory, /skill, and /subagent commands
  35. "command.execute.before": async (input, output) => {
  36. if (input.command === "memory") {
  37. output.parts = await handleMemoryCommand(input.arguments, store, input.sessionID)
  38. }
  39. if (input.command === "skill") {
  40. output.parts = await handleSkillCommand(input.arguments, registry, worktree, input.sessionID)
  41. }
  42. if (input.command === "subagent") {
  43. output.parts = await handleSubAgentCommand(input.arguments, subagents)
  44. }
  45. },
  46. // Auto-memory on session idle
  47. event: async ({ event }) => {
  48. if (event.type === "session.idle") {
  49. const today = new Date().toISOString().slice(0, 10)
  50. store.updateIndex({
  51. date: today,
  52. category: "session",
  53. summary: `Session completed in ${directory}`,
  54. tags: ["auto"],
  55. })
  56. }
  57. },
  58. // Preserve memory context across compaction
  59. "experimental.session.compacting": async (_input, output) => {
  60. const summary = store.loadSummary()
  61. if (summary) {
  62. output.context.push("## Persisted Memory\n" + summary)
  63. }
  64. },
  65. // Shell env: expose KIRINCODE_ env vars
  66. "shell.env": async (_input, output) => {
  67. output.env.KIRINCODE_MEMORY_DIR = store.memoryDir
  68. output.env.KIRINCODE_SUBAGENTS = String(subagents.list().length)
  69. },
  70. // Sub-agent tools for multi-agent task delegation
  71. tool: {
  72. subagent_spawn: tool({
  73. description:
  74. "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.",
  75. args: {
  76. name: tool.schema.string().describe("Unique name for this sub-agent"),
  77. task: tool.schema.string().describe("The task or prompt to send to the sub-agent"),
  78. agent: tool.schema.string().optional().describe("Agent type: build, plan, general, or explore. Defaults to general."),
  79. model: tool.schema.string().optional().describe("Model override, e.g. 'anthropic/claude-sonnet-4-5'"),
  80. },
  81. async execute(args, context) {
  82. const def = subagents.spawn(client, context.sessionID, {
  83. name: args.name,
  84. task: args.task,
  85. agent: args.agent,
  86. model: args.model,
  87. })
  88. return `Sub-agent '${def.name}' spawned (${def.id}). Use subagent_collect '${def.name}' to retrieve results when ready.`
  89. },
  90. }),
  91. subagent_list: tool({
  92. description: "List all spawned sub-agents and their current status. Use after spawning to monitor progress.",
  93. args: {},
  94. async execute() {
  95. const list = subagents.list()
  96. if (list.length === 0) {
  97. return "No sub-agents spawned. Use subagent_spawn to create one."
  98. }
  99. const lines = ["## Sub-Agents", ""]
  100. for (const a of list) {
  101. const icon = a.status === "completed" ? "✅" : a.status === "failed" ? "❌" : a.status === "spawning" ? "⏳" : "🔄"
  102. const taskPreview = a.task.slice(0, 60)
  103. lines.push(`- ${icon} **${a.name}** — ${a.status} — ${taskPreview}${a.task.length > 60 ? "..." : ""}`)
  104. }
  105. return lines.join("\n")
  106. },
  107. }),
  108. subagent_status: tool({
  109. description: "Check the detailed status of a specific sub-agent by name, including elapsed time and any errors.",
  110. args: {
  111. name: tool.schema.string().describe("Name of the sub-agent to check"),
  112. },
  113. async execute(args) {
  114. return subagents.status(args.name)
  115. },
  116. }),
  117. subagent_collect: tool({
  118. description:
  119. "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.",
  120. args: {
  121. name: tool.schema.string().describe("Name of the sub-agent to collect results from"),
  122. },
  123. async execute(args) {
  124. return await subagents.collect(args.name)
  125. },
  126. }),
  127. },
  128. } satisfies Hooks
  129. }
  130. export { MemoryStore, SkillRegistry, SubAgentRegistry }