ManimCat / src /studio-agent /tools /task-tool.ts
Bin29's picture
Sync from main: c1ef036 chore: document docker persistence volumes
94e1b2f
import { createStudioSession } from '../domain/factories'
import type {
StudioToolDefinition,
StudioToolResult,
StudioWorkType
} from '../domain/types'
import { buildChildSessionRules } from '../permissions/policy'
import { inheritStudioSessionMetadata } from '../runtime/session-agent-config'
import type { StudioRuntimeBackedToolContext } from '../runtime/tool-runtime-context'
import { createWorkAndTask, updateTaskAndWork } from '../works/work-lifecycle'
interface TaskToolInput {
subagent_type: 'reviewer' | 'designer'
description: string
input: string
skill?: string
files?: string[]
}
export function createStudioTaskTool(): StudioToolDefinition<TaskToolInput> {
return {
name: 'task',
description: 'Invoke a Studio subagent in a child session.',
category: 'agent',
permission: 'task',
allowedAgents: ['builder'],
requiresTask: true,
execute: async (input, context) => executeTaskTool(input, context as StudioRuntimeBackedToolContext)
}
}
async function executeTaskTool(
input: TaskToolInput,
context: StudioRuntimeBackedToolContext
): Promise<StudioToolResult> {
if (!context.sessionStore) {
throw new Error('Task tool requires a session store')
}
if (!context.runSubagent) {
throw new Error('Task tool requires a subagent runner')
}
await context.ask?.({
permission: 'task',
patterns: [input.subagent_type],
metadata: {
description: input.description,
skill: input.skill
}
})
const childSession = await context.sessionStore.create(
createStudioSession({
projectId: context.projectId,
workspaceId: context.session.workspaceId,
parentSessionId: context.session.id,
studioKind: context.session.studioKind,
agentType: input.subagent_type,
title: `${input.description} (@${input.subagent_type} subagent)`,
directory: context.session.directory,
permissionLevel: context.session.permissionLevel,
permissionRules: buildChildSessionRules({
parentRules: context.session.permissionRules,
denyTask: true
}),
metadata: inheritStudioSessionMetadata(context.session)
})
)
const lifecycleMetadata = {
childSessionId: childSession.id,
subagentType: input.subagent_type,
skill: input.skill,
files: input.files
}
const { work, task } = await createWorkAndTask({
context,
work: {
sessionId: context.session.id,
runId: context.run.id,
type: getWorkType(input.subagent_type),
title: input.description,
status: 'running',
metadata: lifecycleMetadata
},
task: {
sessionId: context.session.id,
runId: context.run.id,
type: 'subagent-run',
status: 'running',
title: input.description,
detail: input.input,
metadata: lifecycleMetadata
},
workMetadata: lifecycleMetadata
})
context.setToolMetadata?.({
title: input.description,
metadata: {
sessionId: childSession.id,
taskId: task?.id,
workId: work?.id,
subagentType: input.subagent_type,
skill: input.skill
}
})
try {
const result = await context.runSubagent({
projectId: context.projectId,
parentSession: context.session,
childSession,
description: input.description,
inputText: input.input,
subagentType: input.subagent_type,
skillName: input.skill,
files: input.files
})
const completed = await updateTaskAndWork({
context,
task,
work,
taskPatch: {
status: 'completed',
metadata: {
...(task?.metadata ?? {}),
...lifecycleMetadata,
result: result.text
}
},
workMetadata: {
...lifecycleMetadata,
result: result.text
}
})
const childText = result.text.trim()
return {
title: input.description,
output: ['task_id: ' + childSession.id, '', '<task_result>', childText, '</task_result>'].join('\n'),
metadata: {
sessionId: childSession.id,
taskId: completed.task?.id ?? task?.id,
workId: completed.work?.id ?? work?.id,
subagentType: input.subagent_type,
skill: input.skill
}
}
} catch (error) {
const failed = await updateTaskAndWork({
context,
task,
work,
taskPatch: {
status: 'failed',
metadata: {
...(task?.metadata ?? {}),
...lifecycleMetadata,
error: error instanceof Error ? error.message : String(error)
}
},
workMetadata: {
...lifecycleMetadata,
error: error instanceof Error ? error.message : String(error)
}
})
context.setToolMetadata?.({
title: input.description,
metadata: {
sessionId: childSession.id,
taskId: failed.task?.id ?? task?.id,
workId: failed.work?.id ?? work?.id,
subagentType: input.subagent_type,
error: error instanceof Error ? error.message : String(error)
}
})
throw error
}
}
function getWorkType(subagentType: TaskToolInput['subagent_type']): StudioWorkType {
return subagentType === 'reviewer' ? 'review' : 'design'
}