feat(task): add team_name routing to task_create tool
- Add optional team_name parameter to task_create - Route to team-namespaced storage when team_name provided - Preserve existing behavior when team_name absent - Add tests for both team and regular task creation - Part of Task 12 (1/4 files complete)
This commit is contained in:
parent
48441b831c
commit
eabc20de9e
@ -10,6 +10,7 @@ const TEST_CONFIG = {
|
|||||||
sisyphus: {
|
sisyphus: {
|
||||||
tasks: {
|
tasks: {
|
||||||
storage_path: TEST_STORAGE,
|
storage_path: TEST_STORAGE,
|
||||||
|
claude_code_compat: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -297,5 +298,41 @@ describe("task_create tool", () => {
|
|||||||
expect(taskContent.subject).toBe("Test task")
|
expect(taskContent.subject).toBe("Test task")
|
||||||
expect(taskContent.description).toBe("Test description")
|
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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import {
|
|||||||
generateTaskId,
|
generateTaskId,
|
||||||
} from "../../features/claude-tasks/storage";
|
} from "../../features/claude-tasks/storage";
|
||||||
import { syncTaskTodoUpdate } from "./todo-sync";
|
import { syncTaskTodoUpdate } from "./todo-sync";
|
||||||
|
import { writeTeamTask } from "../agent-teams/team-task-store";
|
||||||
|
import { getTeamTaskDir } from "../agent-teams/paths";
|
||||||
|
|
||||||
export function createTaskCreateTool(
|
export function createTaskCreateTool(
|
||||||
config: Partial<OhMyOpenCodeConfig>,
|
config: Partial<OhMyOpenCodeConfig>,
|
||||||
@ -48,7 +50,8 @@ Calculate dependencies carefully to maximize parallel execution:
|
|||||||
.optional()
|
.optional()
|
||||||
.describe("Task IDs this task blocks"),
|
.describe("Task IDs this task blocks"),
|
||||||
repoURL: tool.schema.string().optional().describe("Repository URL"),
|
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) => {
|
execute: async (args, context) => {
|
||||||
return handleCreate(args, config, ctx, context);
|
return handleCreate(args, config, ctx, context);
|
||||||
@ -64,42 +67,83 @@ async function handleCreate(
|
|||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const validatedArgs = TaskCreateInputSchema.parse(args);
|
const validatedArgs = TaskCreateInputSchema.parse(args);
|
||||||
const taskDir = getTaskDir(config);
|
if (validatedArgs.team_name) {
|
||||||
const lock = acquireLock(taskDir);
|
const teamName = validatedArgs.team_name as string;
|
||||||
|
const teamTaskDir = getTeamTaskDir(teamName);
|
||||||
|
const lock = acquireLock(teamTaskDir);
|
||||||
|
|
||||||
if (!lock.acquired) {
|
if (!lock.acquired) {
|
||||||
return JSON.stringify({ error: "task_lock_unavailable" });
|
return JSON.stringify({ error: "team_task_lock_unavailable" });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const taskId = generateTaskId();
|
const taskId = generateTaskId();
|
||||||
const task: TaskObject = {
|
const task: TaskObject = {
|
||||||
id: taskId,
|
id: taskId,
|
||||||
subject: validatedArgs.subject,
|
subject: validatedArgs.subject,
|
||||||
description: validatedArgs.description ?? "",
|
description: validatedArgs.description ?? "",
|
||||||
status: "pending",
|
status: "pending",
|
||||||
blocks: validatedArgs.blocks ?? [],
|
blocks: validatedArgs.blocks ?? [],
|
||||||
blockedBy: validatedArgs.blockedBy ?? [],
|
blockedBy: validatedArgs.blockedBy ?? [],
|
||||||
activeForm: validatedArgs.activeForm,
|
activeForm: validatedArgs.activeForm,
|
||||||
metadata: validatedArgs.metadata,
|
metadata: validatedArgs.metadata,
|
||||||
repoURL: validatedArgs.repoURL,
|
repoURL: validatedArgs.repoURL,
|
||||||
parentID: validatedArgs.parentID,
|
parentID: validatedArgs.parentID,
|
||||||
threadID: context.sessionID,
|
threadID: context.sessionID,
|
||||||
};
|
};
|
||||||
|
|
||||||
const validatedTask = TaskObjectSchema.parse(task);
|
const validatedTask = TaskObjectSchema.parse(task);
|
||||||
writeJsonAtomic(join(taskDir, `${taskId}.json`), validatedTask);
|
writeTeamTask(teamName, taskId, validatedTask);
|
||||||
|
|
||||||
await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID);
|
await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID);
|
||||||
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
task: {
|
task: {
|
||||||
id: validatedTask.id,
|
id: validatedTask.id,
|
||||||
subject: validatedTask.subject,
|
subject: validatedTask.subject,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
lock.release();
|
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) {
|
} catch (error) {
|
||||||
if (error instanceof Error && error.message.includes("Required")) {
|
if (error instanceof Error && error.message.includes("Required")) {
|
||||||
|
|||||||
@ -37,6 +37,7 @@ export const TaskCreateInputSchema = z.object({
|
|||||||
metadata: z.record(z.string(), z.unknown()).optional(),
|
metadata: z.record(z.string(), z.unknown()).optional(),
|
||||||
repoURL: z.string().optional(),
|
repoURL: z.string().optional(),
|
||||||
parentID: z.string().optional(),
|
parentID: z.string().optional(),
|
||||||
|
team_name: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type TaskCreateInput = z.infer<typeof TaskCreateInputSchema>
|
export type TaskCreateInput = z.infer<typeof TaskCreateInputSchema>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user