forked from laurentenhoor/devclaw
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtask-comment.ts
More file actions
126 lines (109 loc) · 5.14 KB
/
task-comment.ts
File metadata and controls
126 lines (109 loc) · 5.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/**
* 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 { jsonResult } from "../../json-result.js";
import type { PluginContext } from "../../context.js";
import type { ToolContext } from "../../types.js";
import { log as auditLog } from "../../audit.js";
import { requireWorkspaceDir, resolveChannelId, resolveProject, resolveProvider, autoAssignOwnerLabel, applyNotifyLabel } from "../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(ctx: PluginContext) {
return (toolCtx: 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: { issueId: 42, body: "Found an edge case with null inputs" }
- With role: { issueId: 42, body: "LGTM!", authorRole: "tester" }
- Detailed: { issueId: 42, body: "## Notes\\n\\n- Tested on staging\\n- All checks passing", authorRole: "developer" }`,
parameters: {
type: "object",
required: ["channelId", "issueId", "body"],
properties: {
channelId: {
type: "string",
description: "YOUR chat/group ID — the numeric ID of the chat you are in right now (e.g. '-1003844794417'). Do NOT guess; use the ID of the conversation this message came from.",
},
messageThreadId: {
type: "number",
description: "Optional Telegram forum topic ID for this project (message_thread_id). When provided, resolves the topic-bound project within the chat.",
},
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<string, unknown>) {
const channelId = resolveChannelId(toolCtx, params.channelId as string | undefined);
const messageThreadId = params.messageThreadId as number | undefined;
const issueId = params.issueId as number;
const body = params.body as string;
const authorRole = (params.authorRole as AuthorRole) ?? undefined;
const workspaceDir = requireWorkspaceDir(toolCtx);
if (!body || body.trim().length === 0) {
throw new Error("Comment body cannot be empty.");
}
const channelType = (toolCtx.messageChannel as string | undefined) ?? "telegram";
const accountId = toolCtx.agentAccountId as string | undefined;
const { project } = await resolveProject(workspaceDir, channelId, {
channel: channelType,
accountId,
messageThreadId,
});
const { provider, type: providerType } = await resolveProvider(project, ctx.runCommand);
const issue = await provider.getIssue(issueId);
const commentBody = authorRole
? `${getRoleEmoji(authorRole)} **${authorRole.toUpperCase()}**: ${body}`
: body;
const commentId = await provider.addComment(issueId, commentBody);
// Mark as system-managed (best-effort).
provider.reactToIssueComment(issueId, commentId, "eyes").catch(() => {});
// Apply notify label for channel routing (best-effort).
applyNotifyLabel(provider, issueId, project, channelId, issue.labels);
// Auto-assign owner label to this instance (best-effort).
autoAssignOwnerLabel(workspaceDir, provider, issueId, project).catch(() => {});
await auditLog(workspaceDir, "task_comment", {
project: project.name, 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);
}