feat(delegate-task): add prometheus self-delegation block and delegate_task permission
- Block prometheus from delegating to itself via delegate_task - Grant delegate_task permission to prometheus when called as subagent - Other subagents still have delegate_task disabled
This commit is contained in:
parent
315c75c51e
commit
dee89c1556
@ -1892,4 +1892,250 @@ describe("sisyphus-task", () => {
|
|||||||
expect(resolved!.model).toBe(systemDefaultModel)
|
expect(resolved!.model).toBe(systemDefaultModel)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("prometheus self-delegation block", () => {
|
||||||
|
test("prometheus cannot delegate to prometheus - returns error with guidance", async () => {
|
||||||
|
// #given - current agent is prometheus
|
||||||
|
const { createDelegateTask } = require("./tools")
|
||||||
|
|
||||||
|
const mockManager = { launch: async () => ({}) }
|
||||||
|
const mockClient = {
|
||||||
|
app: { agents: async () => ({ data: [{ name: "prometheus", mode: "subagent" }] }) },
|
||||||
|
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
||||||
|
session: {
|
||||||
|
get: async () => ({ data: { directory: "/project" } }),
|
||||||
|
create: async () => ({ data: { id: "test-session" } }),
|
||||||
|
prompt: async () => ({ data: {} }),
|
||||||
|
messages: async () => ({ data: [] }),
|
||||||
|
status: async () => ({ data: {} }),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const tool = createDelegateTask({
|
||||||
|
manager: mockManager,
|
||||||
|
client: mockClient,
|
||||||
|
})
|
||||||
|
|
||||||
|
const toolContext = {
|
||||||
|
sessionID: "parent-session",
|
||||||
|
messageID: "parent-message",
|
||||||
|
agent: "prometheus",
|
||||||
|
abort: new AbortController().signal,
|
||||||
|
}
|
||||||
|
|
||||||
|
// #when - prometheus tries to delegate to prometheus
|
||||||
|
const result = await tool.execute(
|
||||||
|
{
|
||||||
|
description: "Test self-delegation block",
|
||||||
|
prompt: "Create a plan",
|
||||||
|
subagent_type: "prometheus",
|
||||||
|
run_in_background: false,
|
||||||
|
load_skills: [],
|
||||||
|
},
|
||||||
|
toolContext
|
||||||
|
)
|
||||||
|
|
||||||
|
// #then - should return error telling prometheus to create plan directly
|
||||||
|
expect(result).toContain("prometheus")
|
||||||
|
expect(result).toContain("directly")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("non-prometheus agent CAN delegate to prometheus - proceeds normally", async () => {
|
||||||
|
// #given - current agent is sisyphus
|
||||||
|
const { createDelegateTask } = require("./tools")
|
||||||
|
|
||||||
|
const mockManager = { launch: async () => ({}) }
|
||||||
|
const mockClient = {
|
||||||
|
app: { agents: async () => ({ data: [{ name: "prometheus", mode: "subagent" }] }) },
|
||||||
|
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
||||||
|
session: {
|
||||||
|
get: async () => ({ data: { directory: "/project" } }),
|
||||||
|
create: async () => ({ data: { id: "ses_prometheus_allowed" } }),
|
||||||
|
prompt: async () => ({ data: {} }),
|
||||||
|
messages: async () => ({
|
||||||
|
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Plan created successfully" }] }]
|
||||||
|
}),
|
||||||
|
status: async () => ({ data: { "ses_prometheus_allowed": { type: "idle" } } }),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const tool = createDelegateTask({
|
||||||
|
manager: mockManager,
|
||||||
|
client: mockClient,
|
||||||
|
})
|
||||||
|
|
||||||
|
const toolContext = {
|
||||||
|
sessionID: "parent-session",
|
||||||
|
messageID: "parent-message",
|
||||||
|
agent: "sisyphus",
|
||||||
|
abort: new AbortController().signal,
|
||||||
|
}
|
||||||
|
|
||||||
|
// #when - sisyphus delegates to prometheus
|
||||||
|
const result = await tool.execute(
|
||||||
|
{
|
||||||
|
description: "Test prometheus delegation from non-prometheus agent",
|
||||||
|
prompt: "Create a plan",
|
||||||
|
subagent_type: "prometheus",
|
||||||
|
run_in_background: false,
|
||||||
|
load_skills: [],
|
||||||
|
},
|
||||||
|
toolContext
|
||||||
|
)
|
||||||
|
|
||||||
|
// #then - should proceed normally
|
||||||
|
expect(result).not.toContain("Cannot delegate")
|
||||||
|
expect(result).toContain("Plan created successfully")
|
||||||
|
}, { timeout: 20000 })
|
||||||
|
|
||||||
|
test("case-insensitive: Prometheus (capitalized) cannot delegate to prometheus", async () => {
|
||||||
|
// #given - current agent is Prometheus (capitalized)
|
||||||
|
const { createDelegateTask } = require("./tools")
|
||||||
|
|
||||||
|
const mockManager = { launch: async () => ({}) }
|
||||||
|
const mockClient = {
|
||||||
|
app: { agents: async () => ({ data: [{ name: "prometheus", mode: "subagent" }] }) },
|
||||||
|
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
||||||
|
session: {
|
||||||
|
get: async () => ({ data: { directory: "/project" } }),
|
||||||
|
create: async () => ({ data: { id: "test-session" } }),
|
||||||
|
prompt: async () => ({ data: {} }),
|
||||||
|
messages: async () => ({ data: [] }),
|
||||||
|
status: async () => ({ data: {} }),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const tool = createDelegateTask({
|
||||||
|
manager: mockManager,
|
||||||
|
client: mockClient,
|
||||||
|
})
|
||||||
|
|
||||||
|
const toolContext = {
|
||||||
|
sessionID: "parent-session",
|
||||||
|
messageID: "parent-message",
|
||||||
|
agent: "Prometheus",
|
||||||
|
abort: new AbortController().signal,
|
||||||
|
}
|
||||||
|
|
||||||
|
// #when - Prometheus tries to delegate to prometheus
|
||||||
|
const result = await tool.execute(
|
||||||
|
{
|
||||||
|
description: "Test case-insensitive block",
|
||||||
|
prompt: "Create a plan",
|
||||||
|
subagent_type: "prometheus",
|
||||||
|
run_in_background: false,
|
||||||
|
load_skills: [],
|
||||||
|
},
|
||||||
|
toolContext
|
||||||
|
)
|
||||||
|
|
||||||
|
// #then - should still return error
|
||||||
|
expect(result).toContain("prometheus")
|
||||||
|
expect(result).toContain("directly")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("prometheus subagent delegate_task permission", () => {
|
||||||
|
test("prometheus subagent should have delegate_task permission enabled", async () => {
|
||||||
|
// #given - sisyphus delegates to prometheus
|
||||||
|
const { createDelegateTask } = require("./tools")
|
||||||
|
let promptBody: any
|
||||||
|
|
||||||
|
const mockManager = { launch: async () => ({}) }
|
||||||
|
const mockClient = {
|
||||||
|
app: { agents: async () => ({ data: [{ name: "prometheus", mode: "subagent" }] }) },
|
||||||
|
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
||||||
|
session: {
|
||||||
|
get: async () => ({ data: { directory: "/project" } }),
|
||||||
|
create: async () => ({ data: { id: "ses_prometheus_delegate" } }),
|
||||||
|
prompt: async (input: any) => {
|
||||||
|
promptBody = input.body
|
||||||
|
return { data: {} }
|
||||||
|
},
|
||||||
|
messages: async () => ({
|
||||||
|
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Plan created" }] }]
|
||||||
|
}),
|
||||||
|
status: async () => ({ data: { "ses_prometheus_delegate": { type: "idle" } } }),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const tool = createDelegateTask({
|
||||||
|
manager: mockManager,
|
||||||
|
client: mockClient,
|
||||||
|
})
|
||||||
|
|
||||||
|
const toolContext = {
|
||||||
|
sessionID: "parent-session",
|
||||||
|
messageID: "parent-message",
|
||||||
|
agent: "sisyphus",
|
||||||
|
abort: new AbortController().signal,
|
||||||
|
}
|
||||||
|
|
||||||
|
// #when - sisyphus delegates to prometheus
|
||||||
|
await tool.execute(
|
||||||
|
{
|
||||||
|
description: "Test prometheus delegate_task permission",
|
||||||
|
prompt: "Create a plan",
|
||||||
|
subagent_type: "prometheus",
|
||||||
|
run_in_background: false,
|
||||||
|
load_skills: [],
|
||||||
|
},
|
||||||
|
toolContext
|
||||||
|
)
|
||||||
|
|
||||||
|
// #then - prometheus should have delegate_task permission
|
||||||
|
expect(promptBody.tools.delegate_task).toBe(true)
|
||||||
|
}, { timeout: 20000 })
|
||||||
|
|
||||||
|
test("non-prometheus subagent should NOT have delegate_task permission", async () => {
|
||||||
|
// #given - sisyphus delegates to oracle (non-prometheus)
|
||||||
|
const { createDelegateTask } = require("./tools")
|
||||||
|
let promptBody: any
|
||||||
|
|
||||||
|
const mockManager = { launch: async () => ({}) }
|
||||||
|
const mockClient = {
|
||||||
|
app: { agents: async () => ({ data: [{ name: "oracle", mode: "subagent" }] }) },
|
||||||
|
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
||||||
|
session: {
|
||||||
|
get: async () => ({ data: { directory: "/project" } }),
|
||||||
|
create: async () => ({ data: { id: "ses_oracle_no_delegate" } }),
|
||||||
|
prompt: async (input: any) => {
|
||||||
|
promptBody = input.body
|
||||||
|
return { data: {} }
|
||||||
|
},
|
||||||
|
messages: async () => ({
|
||||||
|
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Consultation done" }] }]
|
||||||
|
}),
|
||||||
|
status: async () => ({ data: { "ses_oracle_no_delegate": { type: "idle" } } }),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const tool = createDelegateTask({
|
||||||
|
manager: mockManager,
|
||||||
|
client: mockClient,
|
||||||
|
})
|
||||||
|
|
||||||
|
const toolContext = {
|
||||||
|
sessionID: "parent-session",
|
||||||
|
messageID: "parent-message",
|
||||||
|
agent: "sisyphus",
|
||||||
|
abort: new AbortController().signal,
|
||||||
|
}
|
||||||
|
|
||||||
|
// #when - sisyphus delegates to oracle
|
||||||
|
await tool.execute(
|
||||||
|
{
|
||||||
|
description: "Test oracle no delegate_task permission",
|
||||||
|
prompt: "Consult on architecture",
|
||||||
|
subagent_type: "oracle",
|
||||||
|
run_in_background: false,
|
||||||
|
load_skills: [],
|
||||||
|
},
|
||||||
|
toolContext
|
||||||
|
)
|
||||||
|
|
||||||
|
// #then - oracle should NOT have delegate_task permission
|
||||||
|
expect(promptBody.tools.delegate_task).toBe(false)
|
||||||
|
}, { timeout: 20000 })
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -768,6 +768,12 @@ To continue this session: session_id="${sessionID}"`
|
|||||||
Sisyphus-Junior is spawned automatically when you specify a category. Pick the appropriate category for your task domain.`
|
Sisyphus-Junior is spawned automatically when you specify a category. Pick the appropriate category for your task domain.`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isPlanAgent(agentName) && isPlanAgent(parentAgent)) {
|
||||||
|
return `You are prometheus. You cannot delegate to prometheus via delegate_task.
|
||||||
|
|
||||||
|
Create the work plan directly - that's your job as the planning agent.`
|
||||||
|
}
|
||||||
|
|
||||||
agentToUse = agentName
|
agentToUse = agentName
|
||||||
|
|
||||||
// Validate agent exists and is callable (not a primary agent)
|
// Validate agent exists and is callable (not a primary agent)
|
||||||
@ -927,6 +933,7 @@ To continue this session: session_id="${task.sessionID}"`
|
|||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const allowDelegateTask = isPlanAgent(agentToUse)
|
||||||
await client.session.prompt({
|
await client.session.prompt({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
body: {
|
body: {
|
||||||
@ -934,7 +941,7 @@ To continue this session: session_id="${task.sessionID}"`
|
|||||||
system: systemContent,
|
system: systemContent,
|
||||||
tools: {
|
tools: {
|
||||||
task: false,
|
task: false,
|
||||||
delegate_task: false,
|
delegate_task: allowDelegateTask,
|
||||||
call_omo_agent: true,
|
call_omo_agent: true,
|
||||||
question: false,
|
question: false,
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user