Răsfoiți Sursa

tweak: adjust way skills are presented to agent to increase likelyhood of skill invocations. (#17053)

Aiden Cline 1 lună în urmă
părinte
comite
0f6bc8ae71

+ 6 - 1
packages/opencode/src/session/prompt.ts

@@ -650,7 +650,12 @@ export namespace SessionPrompt {
       await Plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
 
       // Build system prompt, adding structured output instruction if needed
-      const system = [...(await SystemPrompt.environment(model)), ...(await InstructionPrompt.system())]
+      const skills = await SystemPrompt.skills(agent)
+      const system = [
+        ...(await SystemPrompt.environment(model)),
+        ...(skills ? [skills] : []),
+        ...(await InstructionPrompt.system()),
+      ]
       const format = lastUser.format ?? { type: "text" }
       if (format.type === "json_schema") {
         system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT)

+ 16 - 0
packages/opencode/src/session/system.ts

@@ -10,6 +10,9 @@ import PROMPT_GEMINI from "./prompt/gemini.txt"
 import PROMPT_CODEX from "./prompt/codex_header.txt"
 import PROMPT_TRINITY from "./prompt/trinity.txt"
 import type { Provider } from "@/provider/provider"
+import type { Agent } from "@/agent/agent"
+import { PermissionNext } from "@/permission/next"
+import { Skill } from "@/skill"
 
 export namespace SystemPrompt {
   export function instructions() {
@@ -34,6 +37,7 @@ export namespace SystemPrompt {
         `Here is some useful information about the environment you are running in:`,
         `<env>`,
         `  Working directory: ${Instance.directory}`,
+        `  Workspace root folder: ${Instance.worktree}`,
         `  Is directory a git repo: ${project.vcs === "git" ? "yes" : "no"}`,
         `  Platform: ${process.platform}`,
         `  Today's date: ${new Date().toDateString()}`,
@@ -51,4 +55,16 @@ export namespace SystemPrompt {
       ].join("\n"),
     ]
   }
+
+  export async function skills(agent: Agent.Info) {
+    if (PermissionNext.disabled(["skill"], agent.permission).has("skill")) return
+
+    const list = await Skill.available(agent)
+
+    return [
+      "Skills provide specialized instructions and workflows for specific tasks.",
+      "Use the skill tool to load a skill when a task matches its description.",
+      list.length === 0 ? "No skills are currently available." : "\n" + Skill.fmt(list),
+    ].join("\n")
+  }
 }

+ 23 - 0
packages/opencode/src/skill/skill.ts

@@ -13,6 +13,9 @@ import { Bus } from "@/bus"
 import { Session } from "@/session"
 import { Discovery } from "./discovery"
 import { Glob } from "../util/glob"
+import { pathToFileURL } from "url"
+import type { Agent } from "@/agent/agent"
+import { PermissionNext } from "@/permission/next"
 
 export namespace Skill {
   const log = Log.create({ service: "skill" })
@@ -186,4 +189,24 @@ export namespace Skill {
   export async function dirs() {
     return state().then((x) => x.dirs)
   }
+
+  export async function available(agent?: Agent.Info) {
+    const list = await all()
+    if (!agent) return list
+    return list.filter((skill) => PermissionNext.evaluate("skill", skill.name, agent.permission).action !== "deny")
+  }
+
+  export function fmt(list: Info[]) {
+    return [
+      "<available_skills>",
+      ...list.flatMap((skill) => [
+        `  <skill>`,
+        `    <name>${skill.name}</name>`,
+        `    <description>${skill.description}</description>`,
+        `    <location>${pathToFileURL(skill.location).href}</location>`,
+        `  </skill>`,
+      ]),
+      "</available_skills>",
+    ].join("\n")
+  }
 }

+ 5 - 23
packages/opencode/src/tool/skill.ts

@@ -3,24 +3,14 @@ import { pathToFileURL } from "url"
 import z from "zod"
 import { Tool } from "./tool"
 import { Skill } from "../skill"
-import { PermissionNext } from "../permission/next"
 import { Ripgrep } from "../file/ripgrep"
 import { iife } from "@/util/iife"
 
 export const SkillTool = Tool.define("skill", async (ctx) => {
-  const skills = await Skill.all()
-
-  // Filter skills by agent permissions if agent provided
-  const agent = ctx?.agent
-  const accessibleSkills = agent
-    ? skills.filter((skill) => {
-        const rule = PermissionNext.evaluate("skill", skill.name, agent.permission)
-        return rule.action !== "deny"
-      })
-    : skills
+  const list = await Skill.available(ctx?.agent)
 
   const description =
-    accessibleSkills.length === 0
+    list.length === 0
       ? "Load a specialized skill that provides domain-specific instructions and workflows. No skills are currently available."
       : [
           "Load a specialized skill that provides domain-specific instructions and workflows.",
@@ -34,18 +24,10 @@ export const SkillTool = Tool.define("skill", async (ctx) => {
           "The following skills provide specialized sets of instructions for particular tasks",
           "Invoke this tool to load a skill when a task matches one of the available skills listed below:",
           "",
-          "<available_skills>",
-          ...accessibleSkills.flatMap((skill) => [
-            `  <skill>`,
-            `    <name>${skill.name}</name>`,
-            `    <description>${skill.description}</description>`,
-            `    <location>${pathToFileURL(skill.location).href}</location>`,
-            `  </skill>`,
-          ]),
-          "</available_skills>",
+          Skill.fmt(list),
         ].join("\n")
 
-  const examples = accessibleSkills
+  const examples = list
     .map((skill) => `'${skill.name}'`)
     .slice(0, 3)
     .join(", ")
@@ -62,7 +44,7 @@ export const SkillTool = Tool.define("skill", async (ctx) => {
       const skill = await Skill.get(params.name)
 
       if (!skill) {
-        const available = await Skill.all().then((x) => Object.keys(x).join(", "))
+        const available = await Skill.all().then((x) => x.map((skill) => skill.name).join(", "))
         throw new Error(`Skill "${params.name}" not found. Available skills: ${available || "none"}`)
       }