forked from laurentenhoor/devclaw
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtask-owner.ts
More file actions
145 lines (136 loc) · 5.48 KB
/
task-owner.ts
File metadata and controls
145 lines (136 loc) · 5.48 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/**
* task_owner — Claim issue(s) for this instance.
*
* Adds an `owner:{instanceName}` label to issues so this instance
* owns them for queue scanning and dispatch. Supports claiming a
* single issue or all unclaimed queued issues for a project.
*/
import { jsonResult } from "../../json-result.js";
import type { PluginContext } from "../../context.js";
import type { ToolContext } from "../../types.js";
import { requireWorkspaceDir, resolveChannelId, resolveProject, resolveProvider } from "../helpers.js";
import { loadConfig } from "../../config/index.js";
import { loadInstanceName } from "../../instance.js";
import {
detectOwner,
getOwnerLabel,
OWNER_LABEL_COLOR,
OWNER_LABEL_PREFIX,
getAllQueueLabels,
} from "../../workflow/index.js";
export function createTaskOwnerTool(ctx: PluginContext) {
return (toolCtx: ToolContext) => ({
name: "task_owner",
label: "Task Owner",
description:
"Claim issue(s) for this instance by adding an owner label. " +
"If issueId is given, claims that specific issue. Otherwise claims all unclaimed queued issues. " +
"Use force to transfer ownership from another instance.",
parameters: {
type: "object",
required: ["channelId"],
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:
"Specific issue ID to claim. If omitted, claims all unclaimed queued issues.",
},
force: {
type: "boolean",
description:
"Override existing owner label (transfer ownership). Default: false.",
},
},
},
async execute(_id: string, params: Record<string, unknown>) {
const channelId = resolveChannelId(toolCtx, params.channelId as string | undefined);
const issueIdParam = params.issueId as number | undefined;
const force = (params.force as boolean) ?? false;
const workspaceDir = requireWorkspaceDir(toolCtx);
const messageThreadId = params.messageThreadId as number | undefined;
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 } = await resolveProvider(project, ctx.runCommand);
const resolvedConfig = await loadConfig(workspaceDir, project.name);
const instanceName = await loadInstanceName(
workspaceDir,
resolvedConfig.instanceName,
);
const ownerLabel = getOwnerLabel(instanceName);
// Ensure the owner label exists in the issue tracker
await provider.ensureLabel(ownerLabel, OWNER_LABEL_COLOR);
const claimed: number[] = [];
const skipped: Array<{ issueId: number; reason: string }> = [];
if (issueIdParam !== undefined) {
// Claim a single issue
const issue = await provider.getIssue(issueIdParam);
const currentOwner = detectOwner(issue.labels);
if (currentOwner === instanceName) {
skipped.push({ issueId: issueIdParam, reason: "Already owned by this instance" });
} else if (currentOwner && !force) {
skipped.push({
issueId: issueIdParam,
reason: `Owned by "${currentOwner}". Use force=true to transfer.`,
});
} else {
// Remove old owner label if transferring
if (currentOwner) {
const oldLabel = getOwnerLabel(currentOwner);
await provider.removeLabels(issueIdParam, [oldLabel]);
}
await provider.addLabel(issueIdParam, ownerLabel);
claimed.push(issueIdParam);
}
} else {
// Claim all unclaimed queued issues
const workflow = resolvedConfig.workflow;
const queueLabels = getAllQueueLabels(workflow);
for (const label of queueLabels) {
try {
const issues = await provider.listIssuesByLabel(label);
for (const issue of issues) {
const currentOwner = detectOwner(issue.labels);
if (currentOwner === instanceName) continue; // already ours
if (currentOwner && !force) {
skipped.push({
issueId: issue.iid,
reason: `Owned by "${currentOwner}"`,
});
continue;
}
if (currentOwner) {
const oldLabel = getOwnerLabel(currentOwner);
await provider.removeLabels(issue.iid, [oldLabel]);
}
await provider.addLabel(issue.iid, ownerLabel);
claimed.push(issue.iid);
}
} catch {
// Skip label query failures
}
}
}
return jsonResult({
success: true,
instanceName,
ownerLabel,
claimed,
skipped,
summary: `Claimed ${claimed.length} issue(s) for "${instanceName}"${skipped.length > 0 ? `, ${skipped.length} skipped` : ""}`,
});
},
});
}