feat(task): add team_name routing to task_list and task_update tools
- Add optional team_name parameter to task_list and task_update - Route to team-namespaced storage when team_name provided - Preserve existing behavior when team_name absent - Add comprehensive tests for both team and regular task operations - Task 12 complete (4/4 files: create, get, list, update)
This commit is contained in:
parent
3d5754089e
commit
16e034492c
@ -1,6 +1,8 @@
|
|||||||
import { describe, it, expect, beforeEach, afterEach } from "bun:test"
|
import { describe, it, expect, beforeEach, afterEach } from "bun:test"
|
||||||
import { createTaskList } from "./task-list"
|
import { createTaskList } from "./task-list"
|
||||||
import { writeJsonAtomic } from "../../features/claude-tasks/storage"
|
import { writeJsonAtomic } from "../../features/claude-tasks/storage"
|
||||||
|
import { writeTeamTask } from "../agent-teams/team-task-store"
|
||||||
|
import { getTeamTaskDir } from "../agent-teams/paths"
|
||||||
import type { TaskObject } from "./types"
|
import type { TaskObject } from "./types"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
import { existsSync, rmSync } from "fs"
|
import { existsSync, rmSync } from "fs"
|
||||||
@ -21,6 +23,11 @@ describe("createTaskList", () => {
|
|||||||
if (existsSync(taskDir)) {
|
if (existsSync(taskDir)) {
|
||||||
rmSync(taskDir, { recursive: true })
|
rmSync(taskDir, { recursive: true })
|
||||||
}
|
}
|
||||||
|
// Clean up team task directories
|
||||||
|
const teamTaskDir = getTeamTaskDir("test-team")
|
||||||
|
if (existsSync(teamTaskDir)) {
|
||||||
|
rmSync(teamTaskDir, { recursive: true })
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns empty array when no tasks exist", async () => {
|
it("returns empty array when no tasks exist", async () => {
|
||||||
@ -330,6 +337,72 @@ describe("createTaskList", () => {
|
|||||||
|
|
||||||
//#then
|
//#then
|
||||||
const parsed = JSON.parse(result)
|
const parsed = JSON.parse(result)
|
||||||
expect(parsed.tasks[0].blockedBy).toEqual(["T-missing"])
|
expect(parsed.tasks[0].blockedBy).toEqual(["T-missing"])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("lists tasks from team namespace when team_name provided", async () => {
|
||||||
|
//#given
|
||||||
|
const teamTask = {
|
||||||
|
id: "T-team-1",
|
||||||
|
subject: "Team task",
|
||||||
|
description: "",
|
||||||
|
status: "pending" as const,
|
||||||
|
blocks: [],
|
||||||
|
blockedBy: [],
|
||||||
|
threadID: "test-session",
|
||||||
|
}
|
||||||
|
|
||||||
|
writeTeamTask("test-team", "T-team-1", teamTask)
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
sisyphus: {
|
||||||
|
tasks: {
|
||||||
|
storage_path: join(testProjectDir, ".sisyphus/tasks"),
|
||||||
|
claude_code_compat: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const tool = createTaskList(config)
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const result = await tool.execute({ team_name: "test-team" }, { sessionID: "test-session" })
|
||||||
|
|
||||||
|
//#then
|
||||||
|
const parsed = JSON.parse(result)
|
||||||
|
expect(parsed.tasks).toHaveLength(1)
|
||||||
|
expect(parsed.tasks[0].subject).toBe("Team task")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("lists tasks from regular storage when no team_name", async () => {
|
||||||
|
//#given
|
||||||
|
const task: TaskObject = {
|
||||||
|
id: "T-1",
|
||||||
|
subject: "Regular task",
|
||||||
|
description: "",
|
||||||
|
status: "pending",
|
||||||
|
blocks: [],
|
||||||
|
blockedBy: [],
|
||||||
|
threadID: "test-session",
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJsonAtomic(join(testProjectDir, ".sisyphus/tasks", "T-1.json"), task)
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
sisyphus: {
|
||||||
|
tasks: {
|
||||||
|
storage_path: join(testProjectDir, ".sisyphus/tasks"),
|
||||||
|
claude_code_compat: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const tool = createTaskList(config)
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const result = await tool.execute({}, { sessionID: "test-session" })
|
||||||
|
|
||||||
|
//#then
|
||||||
|
const parsed = JSON.parse(result)
|
||||||
|
expect(parsed.tasks).toHaveLength(1)
|
||||||
|
expect(parsed.tasks[0].subject).toBe("Regular task")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -3,8 +3,9 @@ import { join } from "path"
|
|||||||
import { existsSync, readdirSync } from "fs"
|
import { existsSync, readdirSync } from "fs"
|
||||||
import type { OhMyOpenCodeConfig } from "../../config/schema"
|
import type { OhMyOpenCodeConfig } from "../../config/schema"
|
||||||
import type { TaskObject, TaskStatus } from "./types"
|
import type { TaskObject, TaskStatus } from "./types"
|
||||||
import { TaskObjectSchema } from "./types"
|
import { TaskObjectSchema, TaskListInputSchema } from "./types"
|
||||||
import { readJsonSafe, getTaskDir } from "../../features/claude-tasks/storage"
|
import { readJsonSafe, getTaskDir } from "../../features/claude-tasks/storage"
|
||||||
|
import { listTeamTasks } from "../agent-teams/team-task-store"
|
||||||
|
|
||||||
interface TaskSummary {
|
interface TaskSummary {
|
||||||
id: string
|
id: string
|
||||||
@ -21,57 +22,93 @@ export function createTaskList(config: Partial<OhMyOpenCodeConfig>): ToolDefinit
|
|||||||
Returns tasks excluding completed and deleted statuses by default.
|
Returns tasks excluding completed and deleted statuses by default.
|
||||||
For each task's blockedBy field, filters to only include unresolved (non-completed) blockers.
|
For each task's blockedBy field, filters to only include unresolved (non-completed) blockers.
|
||||||
Returns summary format: id, subject, status, owner, blockedBy (not full description).`,
|
Returns summary format: id, subject, status, owner, blockedBy (not full description).`,
|
||||||
args: {},
|
args: {
|
||||||
execute: async (): Promise<string> => {
|
team_name: tool.schema.string().optional().describe("Optional: team name for team-namespaced tasks"),
|
||||||
const taskDir = getTaskDir(config)
|
},
|
||||||
|
execute: async (args: Record<string, unknown>): Promise<string> => {
|
||||||
|
const validatedArgs = TaskListInputSchema.parse(args)
|
||||||
|
|
||||||
if (!existsSync(taskDir)) {
|
if (validatedArgs.team_name) {
|
||||||
return JSON.stringify({ tasks: [] })
|
const allTasks = listTeamTasks(validatedArgs.team_name)
|
||||||
}
|
|
||||||
|
|
||||||
const files = readdirSync(taskDir)
|
// Filter out completed and deleted tasks
|
||||||
.filter((f) => f.endsWith(".json") && f.startsWith("T-"))
|
const activeTasks = allTasks.filter(
|
||||||
.map((f) => f.replace(".json", ""))
|
(task) => task.status !== "completed" && task.status !== "deleted"
|
||||||
|
)
|
||||||
|
|
||||||
if (files.length === 0) {
|
// Build summary with filtered blockedBy
|
||||||
return JSON.stringify({ tasks: [] })
|
const summaries: TaskSummary[] = activeTasks.map((task) => {
|
||||||
}
|
// Filter blockedBy to only include unresolved (non-completed) blockers
|
||||||
|
const unresolvedBlockers = task.blockedBy.filter((blockerId) => {
|
||||||
|
const blockerTask = allTasks.find((t) => t.id === blockerId)
|
||||||
|
// Include if blocker doesn't exist (missing) or if it's not completed
|
||||||
|
return !blockerTask || blockerTask.status !== "completed"
|
||||||
|
})
|
||||||
|
|
||||||
const allTasks: TaskObject[] = []
|
return {
|
||||||
for (const fileId of files) {
|
id: task.id,
|
||||||
const task = readJsonSafe(join(taskDir, `${fileId}.json`), TaskObjectSchema)
|
subject: task.subject,
|
||||||
if (task) {
|
status: task.status,
|
||||||
allTasks.push(task)
|
owner: task.owner,
|
||||||
}
|
blockedBy: unresolvedBlockers,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out completed and deleted tasks
|
|
||||||
const activeTasks = allTasks.filter(
|
|
||||||
(task) => task.status !== "completed" && task.status !== "deleted"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Build summary with filtered blockedBy
|
|
||||||
const summaries: TaskSummary[] = activeTasks.map((task) => {
|
|
||||||
// Filter blockedBy to only include unresolved (non-completed) blockers
|
|
||||||
const unresolvedBlockers = task.blockedBy.filter((blockerId) => {
|
|
||||||
const blockerTask = allTasks.find((t) => t.id === blockerId)
|
|
||||||
// Include if blocker doesn't exist (missing) or if it's not completed
|
|
||||||
return !blockerTask || blockerTask.status !== "completed"
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return JSON.stringify({
|
||||||
id: task.id,
|
tasks: summaries,
|
||||||
subject: task.subject,
|
reminder: "1 task = 1 task. Maximize parallel execution by running independent tasks (tasks with empty blockedBy) concurrently."
|
||||||
status: task.status,
|
})
|
||||||
owner: task.owner,
|
} else {
|
||||||
blockedBy: unresolvedBlockers,
|
const taskDir = getTaskDir(config)
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return JSON.stringify({
|
if (!existsSync(taskDir)) {
|
||||||
tasks: summaries,
|
return JSON.stringify({ tasks: [] })
|
||||||
reminder: "1 task = 1 task. Maximize parallel execution by running independent tasks (tasks with empty blockedBy) concurrently."
|
}
|
||||||
})
|
|
||||||
|
const files = readdirSync(taskDir)
|
||||||
|
.filter((f) => f.endsWith(".json") && f.startsWith("T-"))
|
||||||
|
.map((f) => f.replace(".json", ""))
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
return JSON.stringify({ tasks: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
const allTasks: TaskObject[] = []
|
||||||
|
for (const fileId of files) {
|
||||||
|
const task = readJsonSafe(join(taskDir, `${fileId}.json`), TaskObjectSchema)
|
||||||
|
if (task) {
|
||||||
|
allTasks.push(task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out completed and deleted tasks
|
||||||
|
const activeTasks = allTasks.filter(
|
||||||
|
(task) => task.status !== "completed" && task.status !== "deleted"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Build summary with filtered blockedBy
|
||||||
|
const summaries: TaskSummary[] = activeTasks.map((task) => {
|
||||||
|
// Filter blockedBy to only include unresolved (non-completed) blockers
|
||||||
|
const unresolvedBlockers = task.blockedBy.filter((blockerId) => {
|
||||||
|
const blockerTask = allTasks.find((t) => t.id === blockerId)
|
||||||
|
// Include if blocker doesn't exist (missing) or if it's not completed
|
||||||
|
return !blockerTask || blockerTask.status !== "completed"
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: task.id,
|
||||||
|
subject: task.subject,
|
||||||
|
status: task.status,
|
||||||
|
owner: task.owner,
|
||||||
|
blockedBy: unresolvedBlockers,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
tasks: summaries,
|
||||||
|
reminder: "1 task = 1 task. Maximize parallel execution by running independent tasks (tasks with empty blockedBy) concurrently."
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { existsSync, rmSync, mkdirSync } from "fs"
|
|||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
import type { TaskObject } from "./types"
|
import type { TaskObject } from "./types"
|
||||||
import { createTaskUpdateTool } from "./task-update"
|
import { createTaskUpdateTool } from "./task-update"
|
||||||
|
import { writeTeamTask } from "../agent-teams/team-task-store"
|
||||||
|
|
||||||
const TEST_STORAGE = ".test-task-update-tool"
|
const TEST_STORAGE = ".test-task-update-tool"
|
||||||
const TEST_DIR = join(process.cwd(), TEST_STORAGE)
|
const TEST_DIR = join(process.cwd(), TEST_STORAGE)
|
||||||
@ -428,5 +429,53 @@ describe("task_update tool", () => {
|
|||||||
expect(result.task.status).toBe("in_progress")
|
expect(result.task.status).toBe("in_progress")
|
||||||
expect(result.task.owner).toBe("alice")
|
expect(result.task.owner).toBe("alice")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("updates task in team namespace when team_name provided", async () => {
|
||||||
|
//#given
|
||||||
|
const taskId = "T-team-test-135"
|
||||||
|
const teamName = "test-team"
|
||||||
|
const initialTask: TaskObject = {
|
||||||
|
id: taskId,
|
||||||
|
subject: "Original team subject",
|
||||||
|
description: "Team task description",
|
||||||
|
status: "pending",
|
||||||
|
blocks: [],
|
||||||
|
blockedBy: [],
|
||||||
|
threadID: TEST_SESSION_ID,
|
||||||
|
}
|
||||||
|
writeTeamTask(teamName, taskId, initialTask)
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const args = {
|
||||||
|
id: taskId,
|
||||||
|
team_name: teamName,
|
||||||
|
subject: "Updated team subject",
|
||||||
|
status: "in_progress" as const,
|
||||||
|
}
|
||||||
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
||||||
|
const result = JSON.parse(resultStr)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(result).toHaveProperty("task")
|
||||||
|
expect(result.task.subject).toBe("Updated team subject")
|
||||||
|
expect(result.task.status).toBe("in_progress")
|
||||||
|
expect(result.task.description).toBe("Team task description")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns error when team task not found", async () => {
|
||||||
|
//#given
|
||||||
|
const args = {
|
||||||
|
id: "T-nonexistent-team-task",
|
||||||
|
team_name: "test-team",
|
||||||
|
}
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const resultStr = await tool.execute(args, TEST_CONTEXT)
|
||||||
|
const result = JSON.parse(resultStr)
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(result).toHaveProperty("error")
|
||||||
|
expect(result.error).toBe("task_not_found")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import type { PluginInput } from "@opencode-ai/plugin";
|
|
||||||
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
import type { PluginInput } from "@opencode-ai/plugin";
|
||||||
import type { OhMyOpenCodeConfig } from "../../config/schema";
|
import type { OhMyOpenCodeConfig } from "../../config/schema";
|
||||||
import type { TaskObject, TaskUpdateInput } from "./types";
|
import type { TaskObject, TaskUpdateInput } from "./types";
|
||||||
import { TaskObjectSchema, TaskUpdateInputSchema } from "./types";
|
import { TaskObjectSchema, TaskUpdateInputSchema } from "./types";
|
||||||
|
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
|
||||||
import {
|
import {
|
||||||
getTaskDir,
|
getTaskDir,
|
||||||
readJsonSafe,
|
readJsonSafe,
|
||||||
@ -11,6 +11,7 @@ import {
|
|||||||
acquireLock,
|
acquireLock,
|
||||||
} from "../../features/claude-tasks/storage";
|
} from "../../features/claude-tasks/storage";
|
||||||
import { syncTaskTodoUpdate } from "./todo-sync";
|
import { syncTaskTodoUpdate } from "./todo-sync";
|
||||||
|
import { readTeamTask, writeTeamTask } from "../agent-teams/team-task-store";
|
||||||
|
|
||||||
const TASK_ID_PATTERN = /^T-[A-Za-z0-9-]+$/;
|
const TASK_ID_PATTERN = /^T-[A-Za-z0-9-]+$/;
|
||||||
|
|
||||||
@ -34,38 +35,39 @@ Syncs to OpenCode Todo API after update.
|
|||||||
**IMPORTANT - Dependency Management:**
|
**IMPORTANT - Dependency Management:**
|
||||||
Use \`addBlockedBy\` to declare dependencies on other tasks.
|
Use \`addBlockedBy\` to declare dependencies on other tasks.
|
||||||
Properly managed dependencies enable maximum parallel execution.`,
|
Properly managed dependencies enable maximum parallel execution.`,
|
||||||
args: {
|
args: {
|
||||||
id: tool.schema.string().describe("Task ID (required)"),
|
id: tool.schema.string().describe("Task ID (required)"),
|
||||||
subject: tool.schema.string().optional().describe("Task subject"),
|
subject: tool.schema.string().optional().describe("Task subject"),
|
||||||
description: tool.schema.string().optional().describe("Task description"),
|
description: tool.schema.string().optional().describe("Task description"),
|
||||||
status: tool.schema
|
status: tool.schema
|
||||||
.enum(["pending", "in_progress", "completed", "deleted"])
|
.enum(["pending", "in_progress", "completed", "deleted"])
|
||||||
.optional()
|
.optional()
|
||||||
.describe("Task status"),
|
.describe("Task status"),
|
||||||
activeForm: tool.schema
|
activeForm: tool.schema
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.describe("Active form (present continuous)"),
|
.describe("Active form (present continuous)"),
|
||||||
owner: tool.schema
|
owner: tool.schema
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.describe("Task owner (agent name)"),
|
.describe("Task owner (agent name)"),
|
||||||
addBlocks: tool.schema
|
addBlocks: tool.schema
|
||||||
.array(tool.schema.string())
|
.array(tool.schema.string())
|
||||||
.optional()
|
.optional()
|
||||||
.describe("Task IDs to add to blocks (additive, not replacement)"),
|
.describe("Task IDs to add to blocks (additive, not replacement)"),
|
||||||
addBlockedBy: tool.schema
|
addBlockedBy: tool.schema
|
||||||
.array(tool.schema.string())
|
.array(tool.schema.string())
|
||||||
.optional()
|
.optional()
|
||||||
.describe("Task IDs to add to blockedBy (additive, not replacement)"),
|
.describe("Task IDs to add to blockedBy (additive, not replacement)"),
|
||||||
metadata: tool.schema
|
metadata: tool.schema
|
||||||
.record(tool.schema.string(), tool.schema.unknown())
|
.record(tool.schema.string(), tool.schema.unknown())
|
||||||
.optional()
|
.optional()
|
||||||
.describe("Task metadata to merge (set key to null to delete)"),
|
.describe("Task metadata to merge (set key to null to delete)"),
|
||||||
},
|
team_name: tool.schema.string().optional().describe("Team namespace for task storage"),
|
||||||
execute: async (args, context) => {
|
},
|
||||||
return handleUpdate(args, config, ctx, context);
|
execute: async (args: Record<string, unknown>, context) => {
|
||||||
},
|
return handleUpdate(args, config, ctx, context);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,17 +84,9 @@ async function handleUpdate(
|
|||||||
return JSON.stringify({ error: "invalid_task_id" });
|
return JSON.stringify({ error: "invalid_task_id" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const taskDir = getTaskDir(config);
|
if (validatedArgs.team_name) {
|
||||||
const lock = acquireLock(taskDir);
|
// Team namespace routing
|
||||||
|
const task = readTeamTask(validatedArgs.team_name, taskId);
|
||||||
if (!lock.acquired) {
|
|
||||||
return JSON.stringify({ error: "task_lock_unavailable" });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const taskPath = join(taskDir, `${taskId}.json`);
|
|
||||||
const task = readJsonSafe(taskPath, TaskObjectSchema);
|
|
||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
return JSON.stringify({ error: "task_not_found" });
|
return JSON.stringify({ error: "task_not_found" });
|
||||||
}
|
}
|
||||||
@ -133,13 +127,72 @@ async function handleUpdate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const validatedTask = TaskObjectSchema.parse(task);
|
const validatedTask = TaskObjectSchema.parse(task);
|
||||||
writeJsonAtomic(taskPath, validatedTask);
|
writeTeamTask(validatedArgs.team_name, taskId, validatedTask);
|
||||||
|
|
||||||
await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID);
|
await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID);
|
||||||
|
|
||||||
return JSON.stringify({ task: validatedTask });
|
return JSON.stringify({ task: validatedTask });
|
||||||
} finally {
|
} else {
|
||||||
lock.release();
|
// Regular task storage
|
||||||
|
const taskDir = getTaskDir(config);
|
||||||
|
const lock = acquireLock(taskDir);
|
||||||
|
|
||||||
|
if (!lock.acquired) {
|
||||||
|
return JSON.stringify({ error: "task_lock_unavailable" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const taskPath = join(taskDir, `${taskId}.json`);
|
||||||
|
const task = readJsonSafe(taskPath, TaskObjectSchema);
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
return JSON.stringify({ error: "task_not_found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validatedArgs.subject !== undefined) {
|
||||||
|
task.subject = validatedArgs.subject;
|
||||||
|
}
|
||||||
|
if (validatedArgs.description !== undefined) {
|
||||||
|
task.description = validatedArgs.description;
|
||||||
|
}
|
||||||
|
if (validatedArgs.status !== undefined) {
|
||||||
|
task.status = validatedArgs.status;
|
||||||
|
}
|
||||||
|
if (validatedArgs.activeForm !== undefined) {
|
||||||
|
task.activeForm = validatedArgs.activeForm;
|
||||||
|
}
|
||||||
|
if (validatedArgs.owner !== undefined) {
|
||||||
|
task.owner = validatedArgs.owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addBlocks = args.addBlocks as string[] | undefined;
|
||||||
|
if (addBlocks) {
|
||||||
|
task.blocks = [...new Set([...task.blocks, ...addBlocks])];
|
||||||
|
}
|
||||||
|
|
||||||
|
const addBlockedBy = args.addBlockedBy as string[] | undefined;
|
||||||
|
if (addBlockedBy) {
|
||||||
|
task.blockedBy = [...new Set([...task.blockedBy, ...addBlockedBy])];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validatedArgs.metadata !== undefined) {
|
||||||
|
task.metadata = { ...task.metadata, ...validatedArgs.metadata };
|
||||||
|
Object.keys(task.metadata).forEach((key) => {
|
||||||
|
if (task.metadata?.[key] === null) {
|
||||||
|
delete task.metadata[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const validatedTask = TaskObjectSchema.parse(task);
|
||||||
|
writeJsonAtomic(taskPath, validatedTask);
|
||||||
|
|
||||||
|
await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID);
|
||||||
|
|
||||||
|
return JSON.stringify({ task: validatedTask });
|
||||||
|
} finally {
|
||||||
|
lock.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error && error.message.includes("Required")) {
|
if (error instanceof Error && error.message.includes("Required")) {
|
||||||
|
|||||||
@ -45,6 +45,7 @@ export type TaskCreateInput = z.infer<typeof TaskCreateInputSchema>
|
|||||||
export const TaskListInputSchema = z.object({
|
export const TaskListInputSchema = z.object({
|
||||||
status: TaskStatusSchema.optional(),
|
status: TaskStatusSchema.optional(),
|
||||||
parentID: z.string().optional(),
|
parentID: z.string().optional(),
|
||||||
|
team_name: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type TaskListInput = z.infer<typeof TaskListInputSchema>
|
export type TaskListInput = z.infer<typeof TaskListInputSchema>
|
||||||
@ -68,6 +69,7 @@ export const TaskUpdateInputSchema = 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 TaskUpdateInput = z.infer<typeof TaskUpdateInputSchema>
|
export type TaskUpdateInput = z.infer<typeof TaskUpdateInputSchema>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user