diff --git a/src/tools/task/task-create.test.ts b/src/tools/task/task-create.test.ts index 5c2758ad1..358cab66f 100644 --- a/src/tools/task/task-create.test.ts +++ b/src/tools/task/task-create.test.ts @@ -10,6 +10,7 @@ const TEST_CONFIG = { sisyphus: { tasks: { storage_path: TEST_STORAGE, + claude_code_compat: true, }, }, } @@ -297,5 +298,41 @@ describe("task_create tool", () => { expect(taskContent.subject).toBe("Test task") expect(taskContent.description).toBe("Test description") }) + + test("creates task in team namespace when team_name provided", async () => { + //#given + const args = { + subject: "Team task", + team_name: "test-team", + } + + //#when + const resultStr = await tool.execute(args, TEST_CONTEXT) + const result = JSON.parse(resultStr) + + //#then + expect(result).toHaveProperty("task") + expect(result.task).toHaveProperty("id") + expect(result.task.subject).toBe("Team task") + }) + + test("creates task in regular storage when no team_name", async () => { + //#given + const args = { + subject: "Regular task", + } + + //#when + const resultStr = await tool.execute(args, TEST_CONTEXT) + const result = JSON.parse(resultStr) + + //#then + expect(result).toHaveProperty("task") + expect(result.task.subject).toBe("Regular task") + // Verify it's in regular storage + const taskId = result.task.id + const taskFile = join(TEST_DIR, `${taskId}.json`) + expect(existsSync(taskFile)).toBe(true) + }) }) }) diff --git a/src/tools/task/task-create.ts b/src/tools/task/task-create.ts index f1eb41888..865116f3d 100644 --- a/src/tools/task/task-create.ts +++ b/src/tools/task/task-create.ts @@ -11,6 +11,8 @@ import { generateTaskId, } from "../../features/claude-tasks/storage"; import { syncTaskTodoUpdate } from "./todo-sync"; +import { writeTeamTask } from "../agent-teams/team-task-store"; +import { getTeamTaskDir } from "../agent-teams/paths"; export function createTaskCreateTool( config: Partial, @@ -48,7 +50,8 @@ Calculate dependencies carefully to maximize parallel execution: .optional() .describe("Task IDs this task blocks"), repoURL: tool.schema.string().optional().describe("Repository URL"), - parentID: tool.schema.string().optional().describe("Parent task ID"), + parentID: tool.schema.string().optional().describe("Parent task ID"), + team_name: tool.schema.string().optional().describe("Optional: team name for team-namespaced tasks"), }, execute: async (args, context) => { return handleCreate(args, config, ctx, context); @@ -64,42 +67,83 @@ async function handleCreate( ): Promise { try { const validatedArgs = TaskCreateInputSchema.parse(args); - const taskDir = getTaskDir(config); - const lock = acquireLock(taskDir); + if (validatedArgs.team_name) { + const teamName = validatedArgs.team_name as string; + const teamTaskDir = getTeamTaskDir(teamName); + const lock = acquireLock(teamTaskDir); - if (!lock.acquired) { - return JSON.stringify({ error: "task_lock_unavailable" }); - } + if (!lock.acquired) { + return JSON.stringify({ error: "team_task_lock_unavailable" }); + } - try { - const taskId = generateTaskId(); - const task: TaskObject = { - id: taskId, - subject: validatedArgs.subject, - description: validatedArgs.description ?? "", - status: "pending", - blocks: validatedArgs.blocks ?? [], - blockedBy: validatedArgs.blockedBy ?? [], - activeForm: validatedArgs.activeForm, - metadata: validatedArgs.metadata, - repoURL: validatedArgs.repoURL, - parentID: validatedArgs.parentID, - threadID: context.sessionID, - }; + try { + const taskId = generateTaskId(); + const task: TaskObject = { + id: taskId, + subject: validatedArgs.subject, + description: validatedArgs.description ?? "", + status: "pending", + blocks: validatedArgs.blocks ?? [], + blockedBy: validatedArgs.blockedBy ?? [], + activeForm: validatedArgs.activeForm, + metadata: validatedArgs.metadata, + repoURL: validatedArgs.repoURL, + parentID: validatedArgs.parentID, + threadID: context.sessionID, + }; - const validatedTask = TaskObjectSchema.parse(task); - writeJsonAtomic(join(taskDir, `${taskId}.json`), validatedTask); + const validatedTask = TaskObjectSchema.parse(task); + writeTeamTask(teamName, taskId, validatedTask); - await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID); + await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID); - return JSON.stringify({ - task: { - id: validatedTask.id, - subject: validatedTask.subject, - }, - }); - } finally { - lock.release(); + return JSON.stringify({ + task: { + id: validatedTask.id, + subject: validatedTask.subject, + }, + }); + } finally { + lock.release(); + } + } else { + const taskDir = getTaskDir(config); + const lock = acquireLock(taskDir); + + if (!lock.acquired) { + return JSON.stringify({ error: "task_lock_unavailable" }); + } + + try { + const taskId = generateTaskId(); + const task: TaskObject = { + id: taskId, + subject: validatedArgs.subject, + description: validatedArgs.description ?? "", + status: "pending", + blocks: validatedArgs.blocks ?? [], + blockedBy: validatedArgs.blockedBy ?? [], + activeForm: validatedArgs.activeForm, + metadata: validatedArgs.metadata, + repoURL: validatedArgs.repoURL, + parentID: validatedArgs.parentID, + threadID: context.sessionID, + }; + + const validatedTask = TaskObjectSchema.parse(task); + writeJsonAtomic(join(taskDir, `${taskId}.json`), validatedTask); + + await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID); + + return JSON.stringify({ + task: { + id: validatedTask.id, + subject: validatedTask.subject, + }, + }); + } finally { + lock.release(); + } } } catch (error) { if (error instanceof Error && error.message.includes("Required")) { diff --git a/src/tools/task/types.ts b/src/tools/task/types.ts index 5258fe603..0762e4f66 100644 --- a/src/tools/task/types.ts +++ b/src/tools/task/types.ts @@ -37,6 +37,7 @@ export const TaskCreateInputSchema = z.object({ metadata: z.record(z.string(), z.unknown()).optional(), repoURL: z.string().optional(), parentID: z.string().optional(), + team_name: z.string().optional(), }) export type TaskCreateInput = z.infer