commands.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import type { Part } from "@kirincode-ai/sdk"
  2. import type { MemoryStore } from "./memory"
  3. import type { SkillRegistry } from "./skills"
  4. import type { SubAgentRegistry } from "./subagent"
  5. let partSeq = 0
  6. function textPart(text: string, sessionID: string): Part {
  7. const id = `kc_part_${Date.now()}_${partSeq++}`
  8. return { id, sessionID, messageID: id, type: "text", text }
  9. }
  10. export async function handleMemoryCommand(args: string, store: MemoryStore, sessionID: string): Promise<Part[]> {
  11. const sub = args.trim()
  12. if (!sub || sub === "list") {
  13. return [textPart(store.format(), sessionID)]
  14. }
  15. if (sub.startsWith("read ")) {
  16. const topic = sub.slice(5).trim()
  17. const content = store.readTopic(topic)
  18. return [textPart(content || `No topic '${topic}' found. Topics: ${store.listTopics().join(", ") || "none"}`, sessionID)]
  19. }
  20. if (sub.startsWith("write ")) {
  21. const rest = sub.slice(6).trim()
  22. const spaceIdx = rest.indexOf(" ")
  23. if (spaceIdx === -1) return [textPart("Usage: /memory write <topic> <content>", sessionID)]
  24. const topic = rest.slice(0, spaceIdx)
  25. const content = rest.slice(spaceIdx + 1)
  26. store.writeTopic(topic, content)
  27. return [textPart(`Topic '${topic}' saved.`, sessionID)]
  28. }
  29. return [textPart("Usage: /memory [list|read <topic>|write <topic> <content>]", sessionID)]
  30. }
  31. export async function handleSkillCommand(args: string, registry: SkillRegistry, worktree: string, sessionID: string): Promise<Part[]> {
  32. const sub = args.trim()
  33. if (!sub || sub === "list") {
  34. const skills = registry.list()
  35. if (skills.length === 0) return [textPart("No skills registered. Use `/skill create <name> <description>` to create one.", sessionID)]
  36. const lines = skills.map((s) => `- **/${s.manifest.name}** [${s.scope}] — ${s.manifest.description}`)
  37. return [textPart(lines.join("\n"), sessionID)]
  38. }
  39. if (sub.startsWith("create ")) {
  40. const rest = sub.slice(7).trim()
  41. const firstQuote = rest.match(/["'](.+?)["']/)
  42. if (firstQuote) {
  43. const name = firstQuote[1] ?? ""
  44. if (!name) return [textPart("Could not parse skill name from quoted string.", sessionID)]
  45. const desc = rest.slice((firstQuote[0] ?? "").length).trim().replace(/^["'](.+?)["']/, "$1")
  46. const path = registry.createSkill(name, desc, `# ${name}\n\nTODO: Write skill instructions here.`, "project", worktree)
  47. registry.load(worktree)
  48. return [textPart(`Skill '/${name}' created at ${path}\nEdit the file to add instructions, then use /skill ${name} to invoke.`, sessionID)]
  49. }
  50. const parts = rest.split(/\s+/)
  51. if (parts.length < 2) return [textPart("Usage: /skill create <name> <description>", sessionID)]
  52. const name = parts[0] ?? ""
  53. if (!name) return [textPart("Usage: /skill create <name> <description>", sessionID)]
  54. const desc = parts.slice(1).join(" ")
  55. const path = registry.createSkill(name, desc, `# ${name}\n\nTODO: Write skill instructions here.`, "project", worktree)
  56. registry.load(worktree)
  57. return [textPart(`Skill '/${name}' created at ${path}`, sessionID)]
  58. }
  59. if (sub === "reload") {
  60. registry.load(worktree)
  61. return [textPart(`Skills reloaded. ${registry.list().length} skill(s) available.`, sessionID)]
  62. }
  63. // Treat as skill name invocation
  64. const content = registry.injectSkill(sub)
  65. return [textPart(content, sessionID)]
  66. }
  67. export async function handleSubAgentCommand(args: string, subagents: SubAgentRegistry, sessionID: string): Promise<Part[]> {
  68. const sub = args.trim()
  69. if (!sub || sub === "list") {
  70. const list = subagents.list()
  71. if (list.length === 0) return [textPart("No sub-agents spawned. Use the subagent_spawn tool to create one.", sessionID)]
  72. const lines = list.map((a) => {
  73. const icon = a.status === "completed" ? "✅" : a.status === "failed" ? "❌" : a.status === "spawning" ? "⏳" : "🔄"
  74. return `- ${icon} **${a.name}** — ${a.status} — ${a.task.slice(0, 60)}${a.task.length > 60 ? "..." : ""}`
  75. })
  76. return [textPart(["## Sub-Agents", "", ...lines].join("\n"), sessionID)]
  77. }
  78. if (sub.startsWith("status ")) {
  79. const name = sub.slice(7).trim()
  80. return [textPart(subagents.status(name), sessionID)]
  81. }
  82. if (sub.startsWith("collect ")) {
  83. const name = sub.slice(8).trim()
  84. const result = await subagents.collect(name)
  85. return [textPart(result, sessionID)]
  86. }
  87. return [textPart("Usage: /subagent [list|status <name>|collect <name>]", sessionID)]
  88. }