refactor(delegate-task): improve session title format and add task_metadata block
- Change session title from 'Task: {desc}' to '{desc} (@{agent} subagent)'
- Move session_id to structured <task_metadata> block for better parsing
- Add category tracking to BackgroundTask type and LaunchInput
- Add tests for new title format and metadata block
This commit is contained in:
parent
d7807072e1
commit
6080bc8caf
@ -138,6 +138,7 @@ export class BackgroundManager {
|
|||||||
parentModel: input.parentModel,
|
parentModel: input.parentModel,
|
||||||
parentAgent: input.parentAgent,
|
parentAgent: input.parentAgent,
|
||||||
model: input.model,
|
model: input.model,
|
||||||
|
category: input.category,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tasks.set(task.id, task)
|
this.tasks.set(task.id, task)
|
||||||
@ -231,7 +232,7 @@ export class BackgroundManager {
|
|||||||
const createResult = await this.client.session.create({
|
const createResult = await this.client.session.create({
|
||||||
body: {
|
body: {
|
||||||
parentID: input.parentSessionID,
|
parentID: input.parentSessionID,
|
||||||
title: `Background: ${input.description}`,
|
title: `${input.description} (@${input.agent} subagent)`,
|
||||||
permission: [
|
permission: [
|
||||||
{ permission: "question", action: "deny" as const, pattern: "*" },
|
{ permission: "question", action: "deny" as const, pattern: "*" },
|
||||||
],
|
],
|
||||||
|
|||||||
@ -38,6 +38,8 @@ export interface BackgroundTask {
|
|||||||
parentAgent?: string
|
parentAgent?: string
|
||||||
/** Marks if the task was launched from an unstable agent/category */
|
/** Marks if the task was launched from an unstable agent/category */
|
||||||
isUnstableAgent?: boolean
|
isUnstableAgent?: boolean
|
||||||
|
/** Category used for this task (e.g., 'quick', 'visual-engineering') */
|
||||||
|
category?: string
|
||||||
|
|
||||||
/** Last message count for stability detection */
|
/** Last message count for stability detection */
|
||||||
lastMsgCount?: number
|
lastMsgCount?: number
|
||||||
@ -57,6 +59,7 @@ export interface LaunchInput {
|
|||||||
isUnstableAgent?: boolean
|
isUnstableAgent?: boolean
|
||||||
skills?: string[]
|
skills?: string[]
|
||||||
skillContent?: string
|
skillContent?: string
|
||||||
|
category?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResumeInput {
|
export interface ResumeInput {
|
||||||
|
|||||||
@ -127,13 +127,16 @@ export async function executeBackgroundContinuation(
|
|||||||
return `Background task continued.
|
return `Background task continued.
|
||||||
|
|
||||||
Task ID: ${task.id}
|
Task ID: ${task.id}
|
||||||
Session ID: ${task.sessionID}
|
|
||||||
Description: ${task.description}
|
Description: ${task.description}
|
||||||
Agent: ${task.agent}
|
Agent: ${task.agent}
|
||||||
Status: ${task.status}
|
Status: ${task.status}
|
||||||
|
|
||||||
Agent continues with full previous context preserved.
|
Agent continues with full previous context preserved.
|
||||||
Use \`background_output\` with task_id="${task.id}" to check progress.`
|
Use \`background_output\` with task_id="${task.id}" to check progress.
|
||||||
|
|
||||||
|
<task_metadata>
|
||||||
|
session_id: ${task.sessionID}
|
||||||
|
</task_metadata>`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return formatDetailedError(error, {
|
return formatDetailedError(error, {
|
||||||
operation: "Continue background task",
|
operation: "Continue background task",
|
||||||
@ -277,14 +280,13 @@ export async function executeSyncContinuation(
|
|||||||
|
|
||||||
return `Task continued and completed in ${duration}.
|
return `Task continued and completed in ${duration}.
|
||||||
|
|
||||||
Session ID: ${args.session_id}
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
${textContent || "(No text output)"}
|
${textContent || "(No text output)"}
|
||||||
|
|
||||||
---
|
<task_metadata>
|
||||||
To continue this session: session_id="${args.session_id}"`
|
session_id: ${args.session_id}
|
||||||
|
</task_metadata>`
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function executeUnstableAgentTask(
|
export async function executeUnstableAgentTask(
|
||||||
@ -311,6 +313,7 @@ export async function executeUnstableAgentTask(
|
|||||||
model: categoryModel,
|
model: categoryModel,
|
||||||
skills: args.load_skills.length > 0 ? args.load_skills : undefined,
|
skills: args.load_skills.length > 0 ? args.load_skills : undefined,
|
||||||
skillContent: systemContent,
|
skillContent: systemContent,
|
||||||
|
category: args.category,
|
||||||
})
|
})
|
||||||
|
|
||||||
const WAIT_FOR_SESSION_INTERVAL_MS = 100
|
const WAIT_FOR_SESSION_INTERVAL_MS = 100
|
||||||
@ -408,7 +411,6 @@ Your run_in_background=false was automatically converted to background mode for
|
|||||||
|
|
||||||
Duration: ${duration}
|
Duration: ${duration}
|
||||||
Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}
|
Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}
|
||||||
Session ID: ${sessionID}
|
|
||||||
|
|
||||||
MONITORING INSTRUCTIONS:
|
MONITORING INSTRUCTIONS:
|
||||||
- The task was monitored and completed successfully
|
- The task was monitored and completed successfully
|
||||||
@ -422,8 +424,9 @@ RESULT:
|
|||||||
|
|
||||||
${textContent || "(No text output)"}
|
${textContent || "(No text output)"}
|
||||||
|
|
||||||
---
|
<task_metadata>
|
||||||
To continue this session: session_id="${sessionID}"`
|
session_id: ${sessionID}
|
||||||
|
</task_metadata>`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return formatDetailedError(error, {
|
return formatDetailedError(error, {
|
||||||
operation: "Launch monitored background task",
|
operation: "Launch monitored background task",
|
||||||
@ -457,6 +460,7 @@ export async function executeBackgroundTask(
|
|||||||
model: categoryModel,
|
model: categoryModel,
|
||||||
skills: args.load_skills.length > 0 ? args.load_skills : undefined,
|
skills: args.load_skills.length > 0 ? args.load_skills : undefined,
|
||||||
skillContent: systemContent,
|
skillContent: systemContent,
|
||||||
|
category: args.category,
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.metadata?.({
|
ctx.metadata?.({
|
||||||
@ -476,13 +480,15 @@ export async function executeBackgroundTask(
|
|||||||
return `Background task launched.
|
return `Background task launched.
|
||||||
|
|
||||||
Task ID: ${task.id}
|
Task ID: ${task.id}
|
||||||
Session ID: ${task.sessionID}
|
|
||||||
Description: ${task.description}
|
Description: ${task.description}
|
||||||
Agent: ${task.agent}${args.category ? ` (category: ${args.category})` : ""}
|
Agent: ${task.agent}${args.category ? ` (category: ${args.category})` : ""}
|
||||||
Status: ${task.status}
|
Status: ${task.status}
|
||||||
|
|
||||||
System notifies on completion. Use \`background_output\` with task_id="${task.id}" to check.
|
System notifies on completion. Use \`background_output\` with task_id="${task.id}" to check.
|
||||||
To continue this session: session_id="${task.sessionID}"`
|
|
||||||
|
<task_metadata>
|
||||||
|
session_id: ${task.sessionID}
|
||||||
|
</task_metadata>`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return formatDetailedError(error, {
|
return formatDetailedError(error, {
|
||||||
operation: "Launch background task",
|
operation: "Launch background task",
|
||||||
@ -517,7 +523,7 @@ export async function executeSyncTask(
|
|||||||
const createResult = await client.session.create({
|
const createResult = await client.session.create({
|
||||||
body: {
|
body: {
|
||||||
parentID: parentContext.sessionID,
|
parentID: parentContext.sessionID,
|
||||||
title: `Task: ${args.description}`,
|
title: `${args.description} (@${agentToUse} subagent)`,
|
||||||
permission: [
|
permission: [
|
||||||
{ permission: "question", action: "deny" as const, pattern: "*" },
|
{ permission: "question", action: "deny" as const, pattern: "*" },
|
||||||
],
|
],
|
||||||
@ -715,14 +721,14 @@ export async function executeSyncTask(
|
|||||||
return `Task completed in ${duration}.
|
return `Task completed in ${duration}.
|
||||||
|
|
||||||
Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}
|
Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}
|
||||||
Session ID: ${sessionID}
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
${textContent || "(No text output)"}
|
${textContent || "(No text output)"}
|
||||||
|
|
||||||
---
|
<task_metadata>
|
||||||
To continue this session: session_id="${sessionID}"`
|
session_id: ${sessionID}
|
||||||
|
</task_metadata>`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (toastManager && taskId !== undefined) {
|
if (toastManager && taskId !== undefined) {
|
||||||
toastManager.removeTask(taskId)
|
toastManager.removeTask(taskId)
|
||||||
|
|||||||
@ -2563,4 +2563,162 @@ describe("sisyphus-task", () => {
|
|||||||
expect(promptBody.tools.delegate_task).toBe(false)
|
expect(promptBody.tools.delegate_task).toBe(false)
|
||||||
}, { timeout: 20000 })
|
}, { timeout: 20000 })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("session title and metadata format (OpenCode compatibility)", () => {
|
||||||
|
test("sync session title follows OpenCode format: '{description} (@{agent} subagent)'", async () => {
|
||||||
|
// given
|
||||||
|
const { createDelegateTask } = require("./tools")
|
||||||
|
let createBody: any
|
||||||
|
|
||||||
|
const mockManager = { launch: async () => ({}) }
|
||||||
|
const mockClient = {
|
||||||
|
app: { agents: async () => ({ data: [] }) },
|
||||||
|
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
||||||
|
model: { list: async () => [{ id: SYSTEM_DEFAULT_MODEL }] },
|
||||||
|
session: {
|
||||||
|
get: async () => ({ data: { directory: "/project" } }),
|
||||||
|
create: async (input: any) => {
|
||||||
|
createBody = input.body
|
||||||
|
return { data: { id: "ses_title_test" } }
|
||||||
|
},
|
||||||
|
prompt: async () => ({ data: {} }),
|
||||||
|
messages: async () => ({
|
||||||
|
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "done" }] }]
|
||||||
|
}),
|
||||||
|
status: async () => ({ data: { "ses_title_test": { type: "idle" } } }),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const tool = createDelegateTask({
|
||||||
|
manager: mockManager,
|
||||||
|
client: mockClient,
|
||||||
|
})
|
||||||
|
|
||||||
|
const toolContext = {
|
||||||
|
sessionID: "parent-session",
|
||||||
|
messageID: "parent-message",
|
||||||
|
agent: "sisyphus",
|
||||||
|
abort: new AbortController().signal,
|
||||||
|
}
|
||||||
|
|
||||||
|
// when - sync task with category
|
||||||
|
await tool.execute(
|
||||||
|
{
|
||||||
|
description: "Implement feature X",
|
||||||
|
prompt: "Build the feature",
|
||||||
|
category: "quick",
|
||||||
|
run_in_background: false,
|
||||||
|
load_skills: [],
|
||||||
|
},
|
||||||
|
toolContext
|
||||||
|
)
|
||||||
|
|
||||||
|
// then - title should follow OpenCode format
|
||||||
|
expect(createBody.title).toBe("Implement feature X (@sisyphus-junior subagent)")
|
||||||
|
}, { timeout: 10000 })
|
||||||
|
|
||||||
|
test("sync task output includes <task_metadata> block with session_id", async () => {
|
||||||
|
// given
|
||||||
|
const { createDelegateTask } = require("./tools")
|
||||||
|
|
||||||
|
const mockManager = { launch: async () => ({}) }
|
||||||
|
const mockClient = {
|
||||||
|
app: { agents: async () => ({ data: [] }) },
|
||||||
|
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
||||||
|
model: { list: async () => [{ id: SYSTEM_DEFAULT_MODEL }] },
|
||||||
|
session: {
|
||||||
|
get: async () => ({ data: { directory: "/project" } }),
|
||||||
|
create: async () => ({ data: { id: "ses_metadata_test" } }),
|
||||||
|
prompt: async () => ({ data: {} }),
|
||||||
|
messages: async () => ({
|
||||||
|
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Task completed" }] }]
|
||||||
|
}),
|
||||||
|
status: async () => ({ data: { "ses_metadata_test": { type: "idle" } } }),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const tool = createDelegateTask({
|
||||||
|
manager: mockManager,
|
||||||
|
client: mockClient,
|
||||||
|
})
|
||||||
|
|
||||||
|
const toolContext = {
|
||||||
|
sessionID: "parent-session",
|
||||||
|
messageID: "parent-message",
|
||||||
|
agent: "sisyphus",
|
||||||
|
abort: new AbortController().signal,
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = await tool.execute(
|
||||||
|
{
|
||||||
|
description: "Test metadata format",
|
||||||
|
prompt: "Do something",
|
||||||
|
category: "quick",
|
||||||
|
run_in_background: false,
|
||||||
|
load_skills: [],
|
||||||
|
},
|
||||||
|
toolContext
|
||||||
|
)
|
||||||
|
|
||||||
|
// then - output should contain <task_metadata> block
|
||||||
|
expect(result).toContain("<task_metadata>")
|
||||||
|
expect(result).toContain("session_id: ses_metadata_test")
|
||||||
|
expect(result).toContain("</task_metadata>")
|
||||||
|
}, { timeout: 10000 })
|
||||||
|
|
||||||
|
test("background task output includes <task_metadata> block with session_id", async () => {
|
||||||
|
// given
|
||||||
|
const { createDelegateTask } = require("./tools")
|
||||||
|
|
||||||
|
const mockManager = {
|
||||||
|
launch: async () => ({
|
||||||
|
id: "bg_meta_test",
|
||||||
|
sessionID: "ses_bg_metadata",
|
||||||
|
description: "Background metadata test",
|
||||||
|
agent: "sisyphus-junior",
|
||||||
|
status: "running",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
const mockClient = {
|
||||||
|
app: { agents: async () => ({ data: [] }) },
|
||||||
|
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
|
||||||
|
model: { list: async () => [{ id: SYSTEM_DEFAULT_MODEL }] },
|
||||||
|
session: {
|
||||||
|
create: async () => ({ data: { id: "ses_bg_metadata" } }),
|
||||||
|
prompt: async () => ({ data: {} }),
|
||||||
|
messages: async () => ({ data: [] }),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const tool = createDelegateTask({
|
||||||
|
manager: mockManager,
|
||||||
|
client: mockClient,
|
||||||
|
})
|
||||||
|
|
||||||
|
const toolContext = {
|
||||||
|
sessionID: "parent-session",
|
||||||
|
messageID: "parent-message",
|
||||||
|
agent: "sisyphus",
|
||||||
|
abort: new AbortController().signal,
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = await tool.execute(
|
||||||
|
{
|
||||||
|
description: "Background metadata test",
|
||||||
|
prompt: "Do something",
|
||||||
|
category: "quick",
|
||||||
|
run_in_background: true,
|
||||||
|
load_skills: [],
|
||||||
|
},
|
||||||
|
toolContext
|
||||||
|
)
|
||||||
|
|
||||||
|
// then - output should contain <task_metadata> block
|
||||||
|
expect(result).toContain("<task_metadata>")
|
||||||
|
expect(result).toContain("session_id: ses_bg_metadata")
|
||||||
|
expect(result).toContain("</task_metadata>")
|
||||||
|
}, { timeout: 10000 })
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user