/** * task_create — Create a new task (issue) in the project's issue tracker. * * Atomically: creates an issue with the specified title, description, and label. * Returns the created issue for immediate pickup if desired. * * Use this when: * - You want to create work items from chat * - A sub-agent finds a bug and needs to file a follow-up issue * - Breaking down an epic into smaller tasks */ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import { jsonResult } from "openclaw/plugin-sdk"; import type { ToolContext } from "../types.js"; import { readProjects } from "../projects.js"; import { createProvider } from "../providers/index.js"; import { log as auditLog } from "../audit.js"; import type { StateLabel } from "../providers/provider.js"; const STATE_LABELS: StateLabel[] = [ "Planning", "To Do", "Doing", "To Test", "Testing", "Done", "To Improve", "Refining", ]; export function createTaskCreateTool(api: OpenClawPluginApi) { return (ctx: ToolContext) => ({ name: "task_create", label: "Task Create", description: `Create a new task (issue) in the project's issue tracker. Use this to file bugs, features, or tasks from chat. Examples: - Simple: { title: "Fix login bug" } - With body: { title: "Add dark mode", description: "## Why\nUsers want dark mode...\n\n## Acceptance Criteria\n- [ ] Toggle in settings" } - Ready for dev: { title: "Implement auth", description: "...", label: "To Do", pickup: true } The issue is created with a state label (defaults to "Planning"). Returns the created issue for immediate pickup.`, parameters: { type: "object", required: ["projectGroupId", "title"], properties: { projectGroupId: { type: "string", description: "Telegram/WhatsApp group ID for the project", }, title: { type: "string", description: "Short, descriptive issue title (e.g., 'Fix login timeout bug')", }, description: { type: "string", description: "Full issue body in markdown. Use for detailed context, acceptance criteria, reproduction steps, links. Supports GitHub-flavored markdown.", }, label: { type: "string", description: `State label for the issue. One of: ${STATE_LABELS.join(", ")}. Defaults to "Planning".`, enum: STATE_LABELS, }, assignees: { type: "array", items: { type: "string" }, description: "GitHub/GitLab usernames to assign (optional)", }, pickup: { type: "boolean", description: "If true, immediately pick up this issue for DEV after creation. Defaults to false.", }, }, }, async execute(_id: string, params: Record) { const groupId = params.projectGroupId as string; const title = params.title as string; const description = (params.description as string) ?? ""; const label = (params.label as StateLabel) ?? "Planning"; const assignees = (params.assignees as string[] | undefined) ?? []; const pickup = (params.pickup as boolean) ?? false; const workspaceDir = ctx.workspaceDir; if (!workspaceDir) { throw new Error("No workspace directory available in tool context"); } // 1. Resolve project const data = await readProjects(workspaceDir); const project = data.projects[groupId]; if (!project) { throw new Error(`Project not found for groupId ${groupId}. Run project_register first.`); } // 2. Create provider const { provider, type: providerType } = createProvider({ repo: project.repo, }); // 3. Create the issue const issue = await provider.createIssue(title, description, label, assignees); // 4. Audit log await auditLog(workspaceDir, "task_create", { project: project.name, groupId, issueId: issue.iid, title, label, provider: providerType, pickup, }); // 5. Build response const hasBody = description && description.trim().length > 0; // Build announcement with URL let announcement = `šŸ“‹ Created #${issue.iid}: "${title}" (${label})`; if (hasBody) { announcement += "\nWith detailed description."; } announcement += `\nšŸ”— ${issue.web_url}`; if (pickup) { announcement += "\nPicking up for DEV..."; } else { announcement += "\nReady for pickup when needed."; } const result = { success: true, issue: { id: issue.iid, title: issue.title, body: hasBody ? description : null, url: issue.web_url, label, }, project: project.name, provider: providerType, pickup, announcement, }; return jsonResult(result); }, }); }