Files
oh-my-openagent/src/tools/task/task-update.ts
YeonGyu-Kim cdf063a0a3 refactor: rename OhMyOpenCode types to OhMyOpenAgent and centralize PACKAGE_NAME
- Rename all PascalCase types: OhMyOpenCodePlugin → OhMyOpenAgentPlugin, etc.
- 231 OhMyOpenAgent references across ~60 files
- Centralize 4 PACKAGE_NAME constants to import from plugin-identity.ts
- Update src/plugin-config.ts to use CONFIG_BASENAME from plugin-identity

Wave 3 Tasks 5 & 6 complete.
2026-03-18 14:01:21 +09:00

153 lines
4.9 KiB
TypeScript

import type { PluginInput } from "@opencode-ai/plugin";
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
import { join } from "path";
import type { OhMyOpenAgentConfig } from "../../config/schema";
import { TaskObjectSchema, TaskUpdateInputSchema } from "./types";
import {
getTaskDir,
readJsonSafe,
writeJsonAtomic,
acquireLock,
} from "../../features/claude-tasks/storage";
import { syncTaskTodoUpdate } from "./todo-sync";
const TASK_ID_PATTERN = /^T-[A-Za-z0-9-]+$/;
function parseTaskId(id: string): string | null {
if (!TASK_ID_PATTERN.test(id)) return null;
return id;
}
export function createTaskUpdateTool(
config: Partial<OhMyOpenAgentConfig>,
ctx?: PluginInput,
): ToolDefinition {
return tool({
description: `Update an existing task with new values.
Supports updating: subject, description, status, activeForm, owner, metadata.
For blocks/blockedBy: use addBlocks/addBlockedBy to append (additive, not replacement).
For metadata: merge with existing, set key to null to delete.
Syncs to OpenCode Todo API after update.
**IMPORTANT - Dependency Management:**
Use \`addBlockedBy\` to declare dependencies on other tasks.
Properly managed dependencies enable maximum parallel execution.`,
args: {
id: tool.schema.string().describe("Task ID (required)"),
subject: tool.schema.string().optional().describe("Task subject"),
description: tool.schema.string().optional().describe("Task description"),
status: tool.schema
.enum(["pending", "in_progress", "completed", "deleted"])
.optional()
.describe("Task status"),
activeForm: tool.schema
.string()
.optional()
.describe("Active form (present continuous)"),
owner: tool.schema
.string()
.optional()
.describe("Task owner (agent name)"),
addBlocks: tool.schema
.array(tool.schema.string())
.optional()
.describe("Task IDs to add to blocks (additive, not replacement)"),
addBlockedBy: tool.schema
.array(tool.schema.string())
.optional()
.describe("Task IDs to add to blockedBy (additive, not replacement)"),
metadata: tool.schema
.record(tool.schema.string(), tool.schema.unknown())
.optional()
.describe("Task metadata to merge (set key to null to delete)"),
},
execute: async (args, context) => {
return handleUpdate(args, config, ctx, context);
},
});
}
async function handleUpdate(
args: Record<string, unknown>,
config: Partial<OhMyOpenAgentConfig>,
ctx: PluginInput | undefined,
context: { sessionID: string },
): Promise<string> {
try {
const validatedArgs = TaskUpdateInputSchema.parse(args);
const taskId = parseTaskId(validatedArgs.id);
if (!taskId) {
return JSON.stringify({ error: "invalid_task_id" });
}
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) {
if (error instanceof Error && error.message.includes("Required")) {
return JSON.stringify({
error: "validation_error",
message: error.message,
});
}
return JSON.stringify({ error: "internal_error" });
}
}