/** * task_comment — Add review comments or notes to an issue. * * Use cases: * - Tester worker adds review feedback without blocking pass/fail * - Developer worker posts implementation notes * - Orchestrator adds summary comments */ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import { jsonResult } from "openclaw/plugin-sdk"; import type { ToolContext } from "../types.js"; import { log as auditLog } from "../audit.js"; import { requireWorkspaceDir, resolveProject, resolveProvider } from "../tool-helpers.js"; import { getAllRoleIds, getFallbackEmoji } from "../roles/index.js"; /** Valid author roles for attribution — all registry roles + orchestrator */ const AUTHOR_ROLES = [...getAllRoleIds(), "orchestrator"]; type AuthorRole = string; export function createTaskCommentTool(api: OpenClawPluginApi) { return (ctx: ToolContext) => ({ name: "task_comment", label: "Task Comment", description: `Add a comment to an issue. Use this for review feedback, implementation notes, or any discussion that doesn't require a state change. Use cases: - Tester adds review feedback without blocking pass/fail - Developer posts implementation notes or progress updates - Orchestrator adds summary comments - Cross-referencing related issues or PRs Examples: - Simple: { projectGroupId: "-123456789", issueId: 42, body: "Found an edge case with null inputs" } - With role: { projectGroupId: "-123456789", issueId: 42, body: "LGTM!", authorRole: "tester" } - Detailed: { projectGroupId: "-123456789", issueId: 42, body: "## Notes\\n\\n- Tested on staging\\n- All checks passing", authorRole: "developer" }`, parameters: { type: "object", required: ["projectGroupId", "issueId", "body"], properties: { projectGroupId: { type: "string", description: "Telegram/WhatsApp group ID (key in projects.json)", }, issueId: { type: "number", description: "Issue ID to comment on", }, body: { type: "string", description: "Comment body in markdown. Supports GitHub-flavored markdown.", }, authorRole: { type: "string", enum: AUTHOR_ROLES, description: `Optional role attribution for the comment. One of: ${AUTHOR_ROLES.join(", ")}`, }, }, }, async execute(_id: string, params: Record) { const groupId = params.projectGroupId as string; const issueId = params.issueId as number; const body = params.body as string; const authorRole = (params.authorRole as AuthorRole) ?? undefined; const workspaceDir = requireWorkspaceDir(ctx); if (!body || body.trim().length === 0) { throw new Error("Comment body cannot be empty."); } const { project } = await resolveProject(workspaceDir, groupId); const { provider, type: providerType } = await resolveProvider(project); const issue = await provider.getIssue(issueId); const commentBody = authorRole ? `${getRoleEmoji(authorRole)} **${authorRole.toUpperCase()}**: ${body}` : body; await provider.addComment(issueId, commentBody); await auditLog(workspaceDir, "task_comment", { project: project.name, groupId, issueId, authorRole: authorRole ?? null, bodyPreview: body.slice(0, 100) + (body.length > 100 ? "..." : ""), provider: providerType, }); return jsonResult({ success: true, issueId, issueTitle: issue.title, issueUrl: issue.web_url, commentAdded: true, authorRole: authorRole ?? null, bodyLength: body.length, project: project.name, provider: providerType, announcement: `💬 Comment added to #${issueId}${authorRole ? ` by ${authorRole.toUpperCase()}` : ""}`, }); }, }); } // --------------------------------------------------------------------------- // Private helpers // --------------------------------------------------------------------------- function getRoleEmoji(role: string): string { if (role === "orchestrator") return "🎛️"; return getFallbackEmoji(role); }