mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-01 14:52:25 +00:00
Added: Ability to hide subagents from primary agents system prompt. (#4773)
Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
This commit is contained in:
@@ -188,6 +188,7 @@ export namespace Agent {
|
||||
item.topP = value.top_p ?? item.topP
|
||||
item.mode = value.mode ?? item.mode
|
||||
item.color = value.color ?? item.color
|
||||
item.hidden = value.hidden ?? item.hidden
|
||||
item.name = value.name ?? item.name
|
||||
item.steps = value.steps ?? item.steps
|
||||
item.options = mergeDeep(item.options, value.options ?? {})
|
||||
|
||||
@@ -465,6 +465,10 @@ export namespace Config {
|
||||
disable: z.boolean().optional(),
|
||||
description: z.string().optional().describe("Description of when to use the agent"),
|
||||
mode: z.enum(["subagent", "primary", "all"]).optional(),
|
||||
hidden: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Hide this subagent from the @ autocomplete menu (default: false, only applies to mode: subagent)"),
|
||||
options: z.record(z.string(), z.any()).optional(),
|
||||
color: z
|
||||
.string()
|
||||
@@ -490,6 +494,7 @@ export namespace Config {
|
||||
"temperature",
|
||||
"top_p",
|
||||
"mode",
|
||||
"hidden",
|
||||
"color",
|
||||
"steps",
|
||||
"maxSteps",
|
||||
|
||||
@@ -232,6 +232,7 @@ export namespace PermissionNext {
|
||||
const result = new Set<string>()
|
||||
for (const tool of tools) {
|
||||
const permission = EDIT_TOOLS.includes(tool) ? "edit" : tool
|
||||
|
||||
const rule = ruleset.findLast((r) => Wildcard.match(permission, r.permission))
|
||||
if (!rule) continue
|
||||
if (rule.pattern === "*" && rule.action === "deny") result.add(tool)
|
||||
|
||||
@@ -37,7 +37,7 @@ import { SessionSummary } from "./summary"
|
||||
import { NamedError } from "@opencode-ai/util/error"
|
||||
import { fn } from "@/util/fn"
|
||||
import { SessionProcessor } from "./processor"
|
||||
import { TaskTool } from "@/tool/task"
|
||||
import { TaskTool, filterSubagents, TASK_DESCRIPTION } from "@/tool/task"
|
||||
import { Tool } from "@/tool/tool"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
import { SessionStatus } from "./status"
|
||||
@@ -382,7 +382,8 @@ export namespace SessionPrompt {
|
||||
messageID: assistantMessage.id,
|
||||
sessionID: sessionID,
|
||||
abort,
|
||||
extra: { bypassAgentCheck: true },
|
||||
callID: part.callID,
|
||||
extra: { userInvokedAgents: [task.agent] },
|
||||
async metadata(input) {
|
||||
await Session.updatePart({
|
||||
...part,
|
||||
@@ -543,12 +544,20 @@ export namespace SessionPrompt {
|
||||
model,
|
||||
abort,
|
||||
})
|
||||
|
||||
// Track agents explicitly invoked by user via @ autocomplete
|
||||
const userInvokedAgents = msgs
|
||||
.filter((m) => m.info.role === "user")
|
||||
.flatMap((m) => m.parts.filter((p) => p.type === "agent") as MessageV2.AgentPart[])
|
||||
.map((p) => p.name)
|
||||
|
||||
const tools = await resolveTools({
|
||||
agent,
|
||||
session,
|
||||
model,
|
||||
tools: lastUser.tools,
|
||||
processor,
|
||||
userInvokedAgents,
|
||||
})
|
||||
|
||||
if (step === 1) {
|
||||
@@ -637,6 +646,7 @@ export namespace SessionPrompt {
|
||||
session: Session.Info
|
||||
tools?: Record<string, boolean>
|
||||
processor: SessionProcessor.Info
|
||||
userInvokedAgents: string[]
|
||||
}) {
|
||||
using _ = log.time("resolveTools")
|
||||
const tools: Record<string, AITool> = {}
|
||||
@@ -646,7 +656,7 @@ export namespace SessionPrompt {
|
||||
abort: options.abortSignal!,
|
||||
messageID: input.processor.message.id,
|
||||
callID: options.toolCallId,
|
||||
extra: { model: input.model },
|
||||
extra: { model: input.model, userInvokedAgents: input.userInvokedAgents },
|
||||
agent: input.agent.name,
|
||||
metadata: async (val: { title?: string; metadata?: any }) => {
|
||||
const match = input.processor.partFromToolCall(options.toolCallId)
|
||||
@@ -789,6 +799,29 @@ export namespace SessionPrompt {
|
||||
}
|
||||
tools[key] = item
|
||||
}
|
||||
|
||||
// Regenerate task tool description with filtered subagents
|
||||
if (tools.task) {
|
||||
const all = await Agent.list().then((x) => x.filter((a) => a.mode !== "primary"))
|
||||
const filtered = filterSubagents(all, input.agent.permission)
|
||||
|
||||
// If no subagents are permitted, remove the task tool entirely
|
||||
if (filtered.length === 0) {
|
||||
delete tools.task
|
||||
} else {
|
||||
const description = TASK_DESCRIPTION.replace(
|
||||
"{agents}",
|
||||
filtered
|
||||
.map((a) => `- ${a.name}: ${a.description ?? "This subagent should only be called manually by the user."}`)
|
||||
.join("\n"),
|
||||
)
|
||||
tools.task = {
|
||||
...tools.task,
|
||||
description,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tools
|
||||
}
|
||||
|
||||
@@ -1098,6 +1131,9 @@ export namespace SessionPrompt {
|
||||
}
|
||||
|
||||
if (part.type === "agent") {
|
||||
// Check if this agent would be denied by task permission
|
||||
const perm = PermissionNext.evaluate("task", part.name, agent.permission)
|
||||
const hint = perm.action === "deny" ? " . Invoked by user; guaranteed to exist." : ""
|
||||
return [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
@@ -1111,9 +1147,12 @@ export namespace SessionPrompt {
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
synthetic: true,
|
||||
// An extra space is added here. Otherwise the 'Use' gets appended
|
||||
// to user's last word; making a combined word
|
||||
text:
|
||||
"Use the above message and context to generate a prompt and call the task tool with subagent: " +
|
||||
part.name,
|
||||
" Use the above message and context to generate a prompt and call the task tool with subagent: " +
|
||||
part.name +
|
||||
hint,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,6 +10,13 @@ import { SessionPrompt } from "../session/prompt"
|
||||
import { iife } from "@/util/iife"
|
||||
import { defer } from "@/util/defer"
|
||||
import { Config } from "../config/config"
|
||||
import { PermissionNext } from "@/permission/next"
|
||||
|
||||
export { DESCRIPTION as TASK_DESCRIPTION }
|
||||
|
||||
export function filterSubagents(agents: Agent.Info[], ruleset: PermissionNext.Ruleset) {
|
||||
return agents.filter((a) => PermissionNext.evaluate("task", a.name, ruleset).action !== "deny")
|
||||
}
|
||||
|
||||
export const TaskTool = Tool.define("task", async () => {
|
||||
const agents = await Agent.list().then((x) => x.filter((a) => a.mode !== "primary"))
|
||||
@@ -30,8 +37,10 @@ export const TaskTool = Tool.define("task", async () => {
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
const config = await Config.get()
|
||||
|
||||
const userInvokedAgents = (ctx.extra?.userInvokedAgents ?? []) as string[]
|
||||
// Skip permission check when invoked from a command subtask (user already approved by invoking the command)
|
||||
if (!ctx.extra?.bypassAgentCheck) {
|
||||
if (!ctx.extra?.bypassAgentCheck && !userInvokedAgents.includes(params.subagent_type)) {
|
||||
await ctx.ask({
|
||||
permission: "task",
|
||||
patterns: [params.subagent_type],
|
||||
|
||||
Reference in New Issue
Block a user