2026-02-02 15:05:07 +09:00
|
|
|
import type { PluginInput } from "@opencode-ai/plugin";
|
|
|
|
|
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
|
|
|
import { join } from "path";
|
|
|
|
|
import type { OhMyOpenCodeConfig } from "../../config/schema";
|
|
|
|
|
import type { TaskObject } from "./types";
|
|
|
|
|
import { TaskObjectSchema, TaskCreateInputSchema } from "./types";
|
2026-02-02 12:01:43 +09:00
|
|
|
import {
|
|
|
|
|
getTaskDir,
|
|
|
|
|
writeJsonAtomic,
|
|
|
|
|
acquireLock,
|
|
|
|
|
generateTaskId,
|
2026-02-02 15:05:07 +09:00
|
|
|
} from "../../features/claude-tasks/storage";
|
|
|
|
|
import { syncTaskTodoUpdate } from "./todo-sync";
|
2026-02-02 12:01:43 +09:00
|
|
|
|
|
|
|
|
export function createTaskCreateTool(
|
2026-02-02 15:05:07 +09:00
|
|
|
config: Partial<OhMyOpenCodeConfig>,
|
|
|
|
|
ctx?: PluginInput,
|
2026-02-02 12:01:43 +09:00
|
|
|
): ToolDefinition {
|
|
|
|
|
return tool({
|
|
|
|
|
description: `Create a new task with auto-generated ID and threadID recording.
|
|
|
|
|
|
|
|
|
|
Auto-generates T-{uuid} ID, records threadID from context, sets status to "pending".
|
|
|
|
|
Returns minimal response with task ID and subject.`,
|
|
|
|
|
args: {
|
|
|
|
|
subject: tool.schema.string().describe("Task subject (required)"),
|
|
|
|
|
description: tool.schema.string().optional().describe("Task description"),
|
2026-02-02 15:05:07 +09:00
|
|
|
activeForm: tool.schema
|
|
|
|
|
.string()
|
|
|
|
|
.optional()
|
|
|
|
|
.describe("Active form (present continuous)"),
|
2026-02-02 12:01:43 +09:00
|
|
|
metadata: tool.schema
|
|
|
|
|
.record(tool.schema.string(), tool.schema.unknown())
|
|
|
|
|
.optional()
|
|
|
|
|
.describe("Task metadata"),
|
|
|
|
|
blockedBy: tool.schema
|
|
|
|
|
.array(tool.schema.string())
|
|
|
|
|
.optional()
|
|
|
|
|
.describe("Task IDs blocking this task"),
|
|
|
|
|
blocks: tool.schema
|
|
|
|
|
.array(tool.schema.string())
|
|
|
|
|
.optional()
|
|
|
|
|
.describe("Task IDs this task blocks"),
|
|
|
|
|
repoURL: tool.schema.string().optional().describe("Repository URL"),
|
|
|
|
|
parentID: tool.schema.string().optional().describe("Parent task ID"),
|
|
|
|
|
},
|
|
|
|
|
execute: async (args, context) => {
|
2026-02-02 15:05:07 +09:00
|
|
|
return handleCreate(args, config, ctx, context);
|
2026-02-02 12:01:43 +09:00
|
|
|
},
|
2026-02-02 15:05:07 +09:00
|
|
|
});
|
2026-02-02 12:01:43 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handleCreate(
|
|
|
|
|
args: Record<string, unknown>,
|
|
|
|
|
config: Partial<OhMyOpenCodeConfig>,
|
2026-02-02 15:05:07 +09:00
|
|
|
ctx: PluginInput | undefined,
|
|
|
|
|
context: { sessionID: string },
|
2026-02-02 12:01:43 +09:00
|
|
|
): Promise<string> {
|
|
|
|
|
try {
|
2026-02-02 15:05:07 +09:00
|
|
|
const validatedArgs = TaskCreateInputSchema.parse(args);
|
|
|
|
|
const taskDir = getTaskDir(config);
|
|
|
|
|
const lock = acquireLock(taskDir);
|
2026-02-02 12:01:43 +09:00
|
|
|
|
|
|
|
|
if (!lock.acquired) {
|
2026-02-02 15:05:07 +09:00
|
|
|
return JSON.stringify({ error: "task_lock_unavailable" });
|
2026-02-02 12:01:43 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2026-02-02 15:05:07 +09:00
|
|
|
const taskId = generateTaskId();
|
2026-02-02 12:01:43 +09:00
|
|
|
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,
|
2026-02-02 15:05:07 +09:00
|
|
|
};
|
2026-02-02 12:01:43 +09:00
|
|
|
|
2026-02-02 15:05:07 +09:00
|
|
|
const validatedTask = TaskObjectSchema.parse(task);
|
|
|
|
|
writeJsonAtomic(join(taskDir, `${taskId}.json`), validatedTask);
|
2026-02-02 12:01:43 +09:00
|
|
|
|
2026-02-02 15:05:07 +09:00
|
|
|
await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID);
|
2026-02-02 12:01:43 +09:00
|
|
|
|
|
|
|
|
return JSON.stringify({
|
|
|
|
|
task: {
|
|
|
|
|
id: validatedTask.id,
|
|
|
|
|
subject: validatedTask.subject,
|
|
|
|
|
},
|
2026-02-02 15:05:07 +09:00
|
|
|
});
|
2026-02-02 12:01:43 +09:00
|
|
|
} finally {
|
2026-02-02 15:05:07 +09:00
|
|
|
lock.release();
|
2026-02-02 12:01:43 +09:00
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error instanceof Error && error.message.includes("Required")) {
|
2026-02-02 15:05:07 +09:00
|
|
|
return JSON.stringify({
|
|
|
|
|
error: "validation_error",
|
|
|
|
|
message: error.message,
|
|
|
|
|
});
|
2026-02-02 12:01:43 +09:00
|
|
|
}
|
2026-02-02 15:05:07 +09:00
|
|
|
return JSON.stringify({ error: "internal_error" });
|
2026-02-02 12:01:43 +09:00
|
|
|
}
|
|
|
|
|
}
|