agent.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. export * as AgentPlugin from "./agent"
  2. import path from "path"
  3. import { define } from "./internal"
  4. import { Effect } from "effect"
  5. import { AgentV2 } from "../agent"
  6. import { Global } from "../global"
  7. import { Location } from "../location"
  8. import { PermissionV2 } from "../permission"
  9. const TRUNCATION_GLOB = path.join(Global.Path.data, "tool-output", "*")
  10. const BUILD_SYSTEM =
  11. "You are KirinCode, an AI coding agent. Help the user accomplish software engineering tasks by inspecting the workspace, making targeted changes, and using tools according to the configured permissions."
  12. const PROMPT_EXPLORE = `You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
  13. Your strengths:
  14. - Rapidly finding files using glob patterns
  15. - Searching code and text with powerful regex patterns
  16. - Reading and analyzing file contents
  17. Guidelines:
  18. - Use Glob for broad file pattern matching
  19. - Use Grep for searching file contents with regex
  20. - Use Read when you know the specific file path you need to read
  21. - Adapt your search approach based on the thoroughness level specified by the caller
  22. - Return file paths as absolute paths in your final response
  23. - For clear communication, avoid using emojis
  24. - Do not create any files, or run bash commands that modify the user's system state in any way
  25. Complete the user's search request efficiently and report your findings clearly.`
  26. const PROMPT_COMPACTION = `You are an anchored context summarization assistant for coding sessions.
  27. Summarize only the conversation history you are given. The newest turns may be kept verbatim outside your summary, so focus on the older context that still matters for continuing the work.
  28. If the prompt includes a <previous-summary> block, treat it as the current anchored summary. Update it with the new history by preserving still-true details, removing stale details, and merging in new facts.
  29. Always follow the exact output structure requested by the user prompt. Keep every section, preserve exact file paths and identifiers when known, and prefer terse bullets over paragraphs.
  30. Do not answer the conversation itself. Do not mention that you are summarizing, compacting, or merging context. Respond in the same language as the conversation.`
  31. const PROMPT_TITLE = `You are a title generator. You output ONLY a thread title. Nothing else.
  32. <task>
  33. Generate a brief title that would help the user find this conversation later.
  34. Follow all rules in <rules>
  35. Use the <examples> so you know what a good title looks like.
  36. Your output must be:
  37. - A single line
  38. - <=50 characters
  39. - No explanations
  40. </task>
  41. <rules>
  42. - you MUST use the same language as the user message you are summarizing
  43. - Title must be grammatically correct and read naturally - no word salad
  44. - Never include tool names in the title (e.g. "read tool", "bash tool", "edit tool")
  45. - Focus on the main topic or question the user needs to retrieve
  46. - Vary your phrasing - avoid repetitive patterns like always starting with "Analyzing"
  47. - When a file is mentioned, focus on WHAT the user wants to do WITH the file, not just that they shared it
  48. - Keep exact: technical terms, numbers, filenames, HTTP codes
  49. - Remove: the, this, my, a, an
  50. - Never assume tech stack
  51. - Never use tools
  52. - NEVER respond to questions, just generate a title for the conversation
  53. - The title should NEVER include "summarizing" or "generating" when generating a title
  54. - DO NOT SAY YOU CANNOT GENERATE A TITLE OR COMPLAIN ABOUT THE INPUT
  55. - Always output something meaningful, even if the input is minimal.
  56. - If the user message is short or conversational (e.g. "hello", "lol", "what's up", "hey"):
  57. -> create a title that reflects the user's tone or intent (such as Greeting, Quick check-in, Light chat, Intro message, etc.)
  58. </rules>
  59. <examples>
  60. "debug 500 errors in production" -> Debugging production 500 errors
  61. "refactor user service" -> Refactoring user service
  62. "why is app.js failing" -> app.js failure investigation
  63. "implement rate limiting" -> Rate limiting implementation
  64. "how do I connect postgres to my API" -> Postgres API connection
  65. "best practices for React hooks" -> React hooks best practices
  66. "@src/credential.ts can you add refresh token support" -> Credential refresh token support
  67. "@utils/parser.ts this is broken" -> Parser bug fix
  68. "look at @config.json" -> Config review
  69. "@App.tsx add dark mode toggle" -> Dark mode toggle in App
  70. </examples>`
  71. const PROMPT_SUMMARY = `Summarize what was done in this conversation. Write like a pull request description.
  72. Rules:
  73. - 2-3 sentences max
  74. - Describe the changes made, not the process
  75. - Do not mention running tests, builds, or other validation steps
  76. - Do not explain what the user asked for
  77. - Write in first person (I added..., I fixed...)
  78. - Never ask questions or add new questions
  79. - If the conversation ends with an unanswered question to the user, preserve that exact question
  80. - If the conversation ends with an imperative statement or request to the user (e.g. "Now please run the command and paste the console output"), always include that exact request in the summary`
  81. export const Plugin = define({
  82. id: "agent",
  83. effect: Effect.fn(function* (ctx) {
  84. const location = yield* Location.Service
  85. const worktree = location.directory
  86. const whitelistedDirs = [TRUNCATION_GLOB, path.join(Global.Path.tmp, "*")]
  87. const readonlyExternalDirectory: PermissionV2.Ruleset = [
  88. { action: "external_directory", resource: "*", effect: "ask" },
  89. ...whitelistedDirs.map(
  90. (resource): PermissionV2.Rule => ({ action: "external_directory", resource, effect: "allow" }),
  91. ),
  92. ]
  93. const defaults: PermissionV2.Ruleset = [
  94. { action: "*", resource: "*", effect: "allow" },
  95. ...readonlyExternalDirectory,
  96. { action: "question", resource: "*", effect: "deny" },
  97. { action: "plan_enter", resource: "*", effect: "deny" },
  98. { action: "plan_exit", resource: "*", effect: "deny" },
  99. { action: "read", resource: "*", effect: "allow" },
  100. { action: "read", resource: "*.env", effect: "ask" },
  101. { action: "read", resource: "*.env.*", effect: "ask" },
  102. { action: "read", resource: "*.env.example", effect: "allow" },
  103. ]
  104. yield* ctx.agent.transform((draft) => {
  105. draft.update(AgentV2.defaultID, (item) => {
  106. item.description = "The default agent. Executes tools based on configured permissions."
  107. item.system ??= BUILD_SYSTEM
  108. item.mode = "primary"
  109. item.permissions.push(
  110. ...PermissionV2.merge(defaults, [
  111. { action: "question", resource: "*", effect: "allow" },
  112. { action: "plan_enter", resource: "*", effect: "allow" },
  113. ]),
  114. )
  115. })
  116. draft.update(AgentV2.ID.make("plan"), (item) => {
  117. item.description = "Plan mode. Disallows all edit tools."
  118. item.mode = "primary"
  119. item.permissions.push(
  120. ...PermissionV2.merge(defaults, [
  121. { action: "question", resource: "*", effect: "allow" },
  122. { action: "plan_exit", resource: "*", effect: "allow" },
  123. { action: "external_directory", resource: path.join(Global.Path.data, "plans", "*"), effect: "allow" },
  124. { action: "edit", resource: "*", effect: "deny" },
  125. { action: "edit", resource: path.join(".kirincode", "plans", "*.md"), effect: "allow" },
  126. {
  127. action: "edit",
  128. resource: path.relative(worktree, path.join(Global.Path.data, "plans", "*.md")),
  129. effect: "allow",
  130. },
  131. ]),
  132. )
  133. })
  134. draft.update(AgentV2.ID.make("general"), (item) => {
  135. item.description =
  136. "General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel."
  137. item.mode = "subagent"
  138. item.permissions.push(...PermissionV2.merge(defaults, [{ action: "todowrite", resource: "*", effect: "deny" }]))
  139. })
  140. draft.update(AgentV2.ID.make("explore"), (item) => {
  141. item.description =
  142. 'Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.'
  143. item.system = PROMPT_EXPLORE
  144. item.mode = "subagent"
  145. item.permissions.push(
  146. ...PermissionV2.merge(
  147. defaults,
  148. [
  149. { action: "*", resource: "*", effect: "deny" },
  150. { action: "grep", resource: "*", effect: "allow" },
  151. { action: "glob", resource: "*", effect: "allow" },
  152. { action: "webfetch", resource: "*", effect: "allow" },
  153. { action: "websearch", resource: "*", effect: "allow" },
  154. { action: "read", resource: "*", effect: "allow" },
  155. ],
  156. readonlyExternalDirectory,
  157. ),
  158. )
  159. })
  160. draft.update(AgentV2.ID.make("compaction"), (item) => {
  161. item.mode = "primary"
  162. item.hidden = true
  163. item.system = PROMPT_COMPACTION
  164. item.permissions.push(...PermissionV2.merge(defaults, [{ action: "*", resource: "*", effect: "deny" }]))
  165. })
  166. draft.update(AgentV2.ID.make("title"), (item) => {
  167. item.mode = "primary"
  168. item.hidden = true
  169. item.system = PROMPT_TITLE
  170. item.permissions.push(...PermissionV2.merge(defaults, [{ action: "*", resource: "*", effect: "deny" }]))
  171. })
  172. draft.update(AgentV2.ID.make("summary"), (item) => {
  173. item.mode = "primary"
  174. item.hidden = true
  175. item.system = PROMPT_SUMMARY
  176. item.permissions.push(...PermissionV2.merge(defaults, [{ action: "*", resource: "*", effect: "deny" }]))
  177. })
  178. })
  179. }),
  180. })