@elizaos/plugin-linear 1.2.13 → 2.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -331
- package/dist/index.js +2199 -2048
- package/dist/index.js.map +27 -1
- package/package.json +22 -23
- package/dist/index.d.ts +0 -89
package/dist/index.js
CHANGED
|
@@ -1,420 +1,105 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.resetTime = resetTime;
|
|
24
|
-
this.name = "LinearRateLimitError";
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// src/services/linear.ts
|
|
29
|
-
var _LinearService = class _LinearService extends Service {
|
|
30
|
-
constructor(runtime) {
|
|
31
|
-
super(runtime);
|
|
32
|
-
this.capabilityDescription = "Linear API integration for issue tracking, project management, and team collaboration";
|
|
33
|
-
this.activityLog = [];
|
|
34
|
-
const apiKey = runtime?.getSetting("LINEAR_API_KEY");
|
|
35
|
-
const workspaceId = runtime?.getSetting("LINEAR_WORKSPACE_ID");
|
|
36
|
-
if (!apiKey) {
|
|
37
|
-
throw new LinearAuthenticationError("Linear API key is required");
|
|
38
|
-
}
|
|
39
|
-
this.linearConfig = {
|
|
40
|
-
LINEAR_API_KEY: apiKey,
|
|
41
|
-
LINEAR_WORKSPACE_ID: workspaceId
|
|
42
|
-
};
|
|
43
|
-
this.workspaceId = workspaceId;
|
|
44
|
-
this.config = {
|
|
45
|
-
LINEAR_API_KEY: apiKey,
|
|
46
|
-
LINEAR_WORKSPACE_ID: workspaceId
|
|
47
|
-
};
|
|
48
|
-
this.client = new LinearClient({
|
|
49
|
-
apiKey: this.linearConfig.LINEAR_API_KEY
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
static async start(runtime) {
|
|
53
|
-
const service = new _LinearService(runtime);
|
|
54
|
-
await service.validateConnection();
|
|
55
|
-
logger.info("Linear service started successfully");
|
|
56
|
-
return service;
|
|
57
|
-
}
|
|
58
|
-
async stop() {
|
|
59
|
-
this.activityLog = [];
|
|
60
|
-
logger.info("Linear service stopped");
|
|
61
|
-
}
|
|
62
|
-
// Validate the API connection
|
|
63
|
-
async validateConnection() {
|
|
64
|
-
try {
|
|
65
|
-
const viewer = await this.client.viewer;
|
|
66
|
-
logger.info(`Linear connected as user: ${viewer.email}`);
|
|
67
|
-
} catch (error) {
|
|
68
|
-
throw new LinearAuthenticationError("Failed to authenticate with Linear API");
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
// Log activity
|
|
72
|
-
logActivity(action, resourceType, resourceId, details, success, error) {
|
|
73
|
-
const activity = {
|
|
74
|
-
id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
75
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
76
|
-
action,
|
|
77
|
-
resource_type: resourceType,
|
|
78
|
-
resource_id: resourceId,
|
|
79
|
-
details,
|
|
80
|
-
success,
|
|
81
|
-
error
|
|
82
|
-
};
|
|
83
|
-
this.activityLog.push(activity);
|
|
84
|
-
if (this.activityLog.length > 1e3) {
|
|
85
|
-
this.activityLog = this.activityLog.slice(-1e3);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
// Get activity log
|
|
89
|
-
getActivityLog(limit, filter) {
|
|
90
|
-
let filtered = [...this.activityLog];
|
|
91
|
-
if (filter) {
|
|
92
|
-
filtered = filtered.filter((item) => {
|
|
93
|
-
return Object.entries(filter).every(([key, value]) => {
|
|
94
|
-
return item[key] === value;
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
return filtered.slice(-(limit || 100));
|
|
99
|
-
}
|
|
100
|
-
// Clear activity log
|
|
101
|
-
clearActivityLog() {
|
|
102
|
-
this.activityLog = [];
|
|
103
|
-
logger.info("Linear activity log cleared");
|
|
104
|
-
}
|
|
105
|
-
// Team operations
|
|
106
|
-
async getTeams() {
|
|
107
|
-
try {
|
|
108
|
-
const teams = await this.client.teams();
|
|
109
|
-
const teamList = await teams.nodes;
|
|
110
|
-
this.logActivity("list_teams", "team", "all", { count: teamList.length }, true);
|
|
111
|
-
return teamList;
|
|
112
|
-
} catch (error) {
|
|
113
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
114
|
-
this.logActivity("list_teams", "team", "all", {}, false, errorMessage);
|
|
115
|
-
throw new LinearAPIError(`Failed to fetch teams: ${errorMessage}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
async getTeam(teamId) {
|
|
119
|
-
try {
|
|
120
|
-
const team = await this.client.team(teamId);
|
|
121
|
-
this.logActivity("get_team", "team", teamId, { name: team.name }, true);
|
|
122
|
-
return team;
|
|
123
|
-
} catch (error) {
|
|
124
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
125
|
-
this.logActivity("get_team", "team", teamId, {}, false, errorMessage);
|
|
126
|
-
throw new LinearAPIError(`Failed to fetch team: ${errorMessage}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
// Issue operations
|
|
130
|
-
async createIssue(input) {
|
|
131
|
-
try {
|
|
132
|
-
const issuePayload = await this.client.createIssue({
|
|
133
|
-
title: input.title,
|
|
134
|
-
description: input.description,
|
|
135
|
-
teamId: input.teamId,
|
|
136
|
-
priority: input.priority,
|
|
137
|
-
assigneeId: input.assigneeId,
|
|
138
|
-
labelIds: input.labelIds,
|
|
139
|
-
projectId: input.projectId,
|
|
140
|
-
stateId: input.stateId,
|
|
141
|
-
estimate: input.estimate,
|
|
142
|
-
dueDate: input.dueDate
|
|
143
|
-
});
|
|
144
|
-
const issue = await issuePayload.issue;
|
|
145
|
-
if (!issue) {
|
|
146
|
-
throw new Error("Failed to create issue");
|
|
147
|
-
}
|
|
148
|
-
this.logActivity("create_issue", "issue", issue.id, {
|
|
149
|
-
title: input.title,
|
|
150
|
-
teamId: input.teamId
|
|
151
|
-
}, true);
|
|
152
|
-
return issue;
|
|
153
|
-
} catch (error) {
|
|
154
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
155
|
-
this.logActivity("create_issue", "issue", "new", input, false, errorMessage);
|
|
156
|
-
throw new LinearAPIError(`Failed to create issue: ${errorMessage}`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
async getIssue(issueId) {
|
|
160
|
-
try {
|
|
161
|
-
const issue = await this.client.issue(issueId);
|
|
162
|
-
this.logActivity("get_issue", "issue", issueId, {
|
|
163
|
-
title: issue.title,
|
|
164
|
-
identifier: issue.identifier
|
|
165
|
-
}, true);
|
|
166
|
-
return issue;
|
|
167
|
-
} catch (error) {
|
|
168
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
169
|
-
this.logActivity("get_issue", "issue", issueId, {}, false, errorMessage);
|
|
170
|
-
throw new LinearAPIError(`Failed to fetch issue: ${errorMessage}`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
async updateIssue(issueId, updates) {
|
|
174
|
-
try {
|
|
175
|
-
const updatePayload = await this.client.updateIssue(issueId, {
|
|
176
|
-
title: updates.title,
|
|
177
|
-
description: updates.description,
|
|
178
|
-
priority: updates.priority,
|
|
179
|
-
assigneeId: updates.assigneeId,
|
|
180
|
-
labelIds: updates.labelIds,
|
|
181
|
-
projectId: updates.projectId,
|
|
182
|
-
stateId: updates.stateId,
|
|
183
|
-
estimate: updates.estimate,
|
|
184
|
-
dueDate: updates.dueDate
|
|
185
|
-
});
|
|
186
|
-
const issue = await updatePayload.issue;
|
|
187
|
-
if (!issue) {
|
|
188
|
-
throw new Error("Failed to update issue");
|
|
1
|
+
// src/actions/clearActivity.ts
|
|
2
|
+
import {
|
|
3
|
+
logger
|
|
4
|
+
} from "@elizaos/core";
|
|
5
|
+
var clearActivityAction = {
|
|
6
|
+
name: "CLEAR_LINEAR_ACTIVITY",
|
|
7
|
+
description: "Clear the Linear activity log",
|
|
8
|
+
similes: ["clear-linear-activity", "reset-linear-activity", "delete-linear-activity"],
|
|
9
|
+
examples: [
|
|
10
|
+
[
|
|
11
|
+
{
|
|
12
|
+
name: "User",
|
|
13
|
+
content: {
|
|
14
|
+
text: "Clear the Linear activity log"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "Assistant",
|
|
19
|
+
content: {
|
|
20
|
+
text: "I'll clear the Linear activity log for you.",
|
|
21
|
+
actions: ["CLEAR_LINEAR_ACTIVITY"]
|
|
22
|
+
}
|
|
189
23
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
{
|
|
27
|
+
name: "User",
|
|
28
|
+
content: {
|
|
29
|
+
text: "Reset Linear activity"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "Assistant",
|
|
34
|
+
content: {
|
|
35
|
+
text: "I'll reset the Linear activity log now.",
|
|
36
|
+
actions: ["CLEAR_LINEAR_ACTIVITY"]
|
|
37
|
+
}
|
|
204
38
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
async searchIssues(filters) {
|
|
39
|
+
]
|
|
40
|
+
],
|
|
41
|
+
async validate(runtime, _message, _state) {
|
|
42
|
+
const apiKey = runtime.getSetting("LINEAR_API_KEY");
|
|
43
|
+
return !!apiKey;
|
|
44
|
+
},
|
|
45
|
+
async handler(runtime, message, _state, _options, callback) {
|
|
213
46
|
try {
|
|
214
|
-
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
{ title: { containsIgnoreCase: filters.query } },
|
|
218
|
-
{ description: { containsIgnoreCase: filters.query } }
|
|
219
|
-
];
|
|
220
|
-
}
|
|
221
|
-
if (filters.team) {
|
|
222
|
-
const teams = await this.getTeams();
|
|
223
|
-
const team = teams.find(
|
|
224
|
-
(t) => t.key.toLowerCase() === filters.team?.toLowerCase() || t.name.toLowerCase() === filters.team?.toLowerCase()
|
|
225
|
-
);
|
|
226
|
-
if (team) {
|
|
227
|
-
filterObject.team = { id: { eq: team.id } };
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
if (filters.assignee && filters.assignee.length > 0) {
|
|
231
|
-
const users = await this.getUsers();
|
|
232
|
-
const assigneeIds = filters.assignee.map((assigneeName) => {
|
|
233
|
-
const user = users.find(
|
|
234
|
-
(u) => u.email === assigneeName || u.name.toLowerCase().includes(assigneeName.toLowerCase())
|
|
235
|
-
);
|
|
236
|
-
return user?.id;
|
|
237
|
-
}).filter(Boolean);
|
|
238
|
-
if (assigneeIds.length > 0) {
|
|
239
|
-
filterObject.assignee = { id: { in: assigneeIds } };
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
if (filters.priority && filters.priority.length > 0) {
|
|
243
|
-
filterObject.priority = { number: { in: filters.priority } };
|
|
244
|
-
}
|
|
245
|
-
if (filters.state && filters.state.length > 0) {
|
|
246
|
-
filterObject.state = {
|
|
247
|
-
name: { in: filters.state }
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
if (filters.label && filters.label.length > 0) {
|
|
251
|
-
filterObject.labels = {
|
|
252
|
-
some: {
|
|
253
|
-
name: { in: filters.label }
|
|
254
|
-
}
|
|
255
|
-
};
|
|
47
|
+
const linearService = runtime.getService("linear");
|
|
48
|
+
if (!linearService) {
|
|
49
|
+
throw new Error("Linear service not available");
|
|
256
50
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const issueList = await issues.nodes;
|
|
263
|
-
this.logActivity("search_issues", "issue", "search", {
|
|
264
|
-
filters,
|
|
265
|
-
count: issueList.length
|
|
266
|
-
}, true);
|
|
267
|
-
return issueList;
|
|
268
|
-
} catch (error) {
|
|
269
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
270
|
-
this.logActivity("search_issues", "issue", "search", filters, false, errorMessage);
|
|
271
|
-
throw new LinearAPIError(`Failed to search issues: ${errorMessage}`);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
// Comment operations
|
|
275
|
-
async createComment(input) {
|
|
276
|
-
try {
|
|
277
|
-
const commentPayload = await this.client.createComment({
|
|
278
|
-
body: input.body,
|
|
279
|
-
issueId: input.issueId
|
|
280
|
-
});
|
|
281
|
-
const comment = await commentPayload.comment;
|
|
282
|
-
if (!comment) {
|
|
283
|
-
throw new Error("Failed to create comment");
|
|
284
|
-
}
|
|
285
|
-
this.logActivity("create_comment", "comment", comment.id, {
|
|
286
|
-
issueId: input.issueId,
|
|
287
|
-
bodyLength: input.body.length
|
|
288
|
-
}, true);
|
|
289
|
-
return comment;
|
|
290
|
-
} catch (error) {
|
|
291
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
292
|
-
this.logActivity("create_comment", "comment", "new", input, false, errorMessage);
|
|
293
|
-
throw new LinearAPIError(`Failed to create comment: ${errorMessage}`);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
// Project operations
|
|
297
|
-
async getProjects(teamId) {
|
|
298
|
-
try {
|
|
299
|
-
const query = this.client.projects({
|
|
300
|
-
first: 100
|
|
301
|
-
});
|
|
302
|
-
const projects = await query;
|
|
303
|
-
let projectList = await projects.nodes;
|
|
304
|
-
if (teamId) {
|
|
305
|
-
const filteredProjects = await Promise.all(
|
|
306
|
-
projectList.map(async (project) => {
|
|
307
|
-
const projectTeams = await project.teams();
|
|
308
|
-
const teamsList = await projectTeams.nodes;
|
|
309
|
-
const hasTeam = teamsList.some((team) => team.id === teamId);
|
|
310
|
-
return hasTeam ? project : null;
|
|
311
|
-
})
|
|
312
|
-
);
|
|
313
|
-
projectList = filteredProjects.filter(Boolean);
|
|
314
|
-
}
|
|
315
|
-
this.logActivity("list_projects", "project", "all", {
|
|
316
|
-
count: projectList.length,
|
|
317
|
-
teamId
|
|
318
|
-
}, true);
|
|
319
|
-
return projectList;
|
|
320
|
-
} catch (error) {
|
|
321
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
322
|
-
this.logActivity("list_projects", "project", "all", { teamId }, false, errorMessage);
|
|
323
|
-
throw new LinearAPIError(`Failed to fetch projects: ${errorMessage}`);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
async getProject(projectId) {
|
|
327
|
-
try {
|
|
328
|
-
const project = await this.client.project(projectId);
|
|
329
|
-
this.logActivity("get_project", "project", projectId, {
|
|
330
|
-
name: project.name
|
|
331
|
-
}, true);
|
|
332
|
-
return project;
|
|
333
|
-
} catch (error) {
|
|
334
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
335
|
-
this.logActivity("get_project", "project", projectId, {}, false, errorMessage);
|
|
336
|
-
throw new LinearAPIError(`Failed to fetch project: ${errorMessage}`);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
// User operations
|
|
340
|
-
async getUsers() {
|
|
341
|
-
try {
|
|
342
|
-
const users = await this.client.users();
|
|
343
|
-
const userList = await users.nodes;
|
|
344
|
-
this.logActivity("list_users", "user", "all", {
|
|
345
|
-
count: userList.length
|
|
346
|
-
}, true);
|
|
347
|
-
return userList;
|
|
348
|
-
} catch (error) {
|
|
349
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
350
|
-
this.logActivity("list_users", "user", "all", {}, false, errorMessage);
|
|
351
|
-
throw new LinearAPIError(`Failed to fetch users: ${errorMessage}`);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
async getCurrentUser() {
|
|
355
|
-
try {
|
|
356
|
-
const user = await this.client.viewer;
|
|
357
|
-
this.logActivity("get_current_user", "user", user.id, {
|
|
358
|
-
email: user.email,
|
|
359
|
-
name: user.name
|
|
360
|
-
}, true);
|
|
361
|
-
return user;
|
|
362
|
-
} catch (error) {
|
|
363
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
364
|
-
this.logActivity("get_current_user", "user", "current", {}, false, errorMessage);
|
|
365
|
-
throw new LinearAPIError(`Failed to fetch current user: ${errorMessage}`);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
// Label operations
|
|
369
|
-
async getLabels(teamId) {
|
|
370
|
-
try {
|
|
371
|
-
const query = this.client.issueLabels({
|
|
372
|
-
first: 100,
|
|
373
|
-
filter: teamId ? {
|
|
374
|
-
team: { id: { eq: teamId } }
|
|
375
|
-
} : void 0
|
|
51
|
+
await linearService.clearActivityLog();
|
|
52
|
+
const successMessage = "✅ Linear activity log has been cleared.";
|
|
53
|
+
await callback?.({
|
|
54
|
+
text: successMessage,
|
|
55
|
+
source: message.content.source
|
|
376
56
|
});
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
teamId
|
|
382
|
-
}, true);
|
|
383
|
-
return labelList;
|
|
57
|
+
return {
|
|
58
|
+
text: successMessage,
|
|
59
|
+
success: true
|
|
60
|
+
};
|
|
384
61
|
} catch (error) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
// Workflow state operations
|
|
391
|
-
async getWorkflowStates(teamId) {
|
|
392
|
-
try {
|
|
393
|
-
const states = await this.client.workflowStates({
|
|
394
|
-
filter: {
|
|
395
|
-
team: { id: { eq: teamId } }
|
|
396
|
-
}
|
|
62
|
+
logger.error("Failed to clear Linear activity:", error);
|
|
63
|
+
const errorMessage = `❌ Failed to clear Linear activity: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
64
|
+
await callback?.({
|
|
65
|
+
text: errorMessage,
|
|
66
|
+
source: message.content.source
|
|
397
67
|
});
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
return stateList;
|
|
403
|
-
} catch (error) {
|
|
404
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
405
|
-
this.logActivity("list_workflow_states", "team", teamId, {}, false, errorMessage);
|
|
406
|
-
throw new LinearAPIError(`Failed to fetch workflow states: ${errorMessage}`);
|
|
68
|
+
return {
|
|
69
|
+
text: errorMessage,
|
|
70
|
+
success: false
|
|
71
|
+
};
|
|
407
72
|
}
|
|
408
73
|
}
|
|
409
74
|
};
|
|
410
|
-
_LinearService.serviceType = "linear";
|
|
411
|
-
var LinearService = _LinearService;
|
|
412
75
|
|
|
413
|
-
// src/actions/
|
|
76
|
+
// src/actions/createComment.ts
|
|
414
77
|
import {
|
|
415
|
-
|
|
416
|
-
|
|
78
|
+
logger as logger2,
|
|
79
|
+
ModelType
|
|
417
80
|
} from "@elizaos/core";
|
|
81
|
+
|
|
82
|
+
// src/generated/prompts/typescript/prompts.ts
|
|
83
|
+
var createCommentTemplate = `Extract comment details from the user's request to add a comment to a Linear issue.
|
|
84
|
+
|
|
85
|
+
User request: "{{userMessage}}"
|
|
86
|
+
|
|
87
|
+
The user might express this in various ways:
|
|
88
|
+
- "Comment on ENG-123: This looks good"
|
|
89
|
+
- "Tell ENG-123 that the fix is ready for testing"
|
|
90
|
+
- "Add a note to the login bug saying we need more info"
|
|
91
|
+
- "Reply to COM2-7: Thanks for the update"
|
|
92
|
+
- "Let the payment issue know that it's blocked by API changes"
|
|
93
|
+
|
|
94
|
+
Return ONLY a JSON object:
|
|
95
|
+
{
|
|
96
|
+
"issueId": "Direct issue ID if explicitly mentioned (e.g., ENG-123)",
|
|
97
|
+
"issueDescription": "Description/keywords of the issue if no ID provided",
|
|
98
|
+
"commentBody": "The actual comment content to add",
|
|
99
|
+
"commentType": "note/reply/update/question/feedback (inferred from context)"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Extract the core message the user wants to convey as the comment body.`;
|
|
418
103
|
var createIssueTemplate = `Given the user's request, extract the information needed to create a Linear issue.
|
|
419
104
|
|
|
420
105
|
User request: "{{userMessage}}"
|
|
@@ -430,222 +115,44 @@ Extract and return ONLY a JSON object (no markdown formatting, no code blocks) w
|
|
|
430
115
|
}
|
|
431
116
|
|
|
432
117
|
Return only the JSON object, no other text.`;
|
|
433
|
-
var
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
text: "I'll create a bug report for the engineering team right away.",
|
|
462
|
-
actions: ["CREATE_LINEAR_ISSUE"]
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
]],
|
|
466
|
-
async validate(runtime, _message, _state) {
|
|
467
|
-
try {
|
|
468
|
-
const apiKey = runtime.getSetting("LINEAR_API_KEY");
|
|
469
|
-
return !!apiKey;
|
|
470
|
-
} catch {
|
|
471
|
-
return false;
|
|
472
|
-
}
|
|
118
|
+
var deleteIssueTemplate = `Given the user's request to delete/archive a Linear issue, extract the issue identifier.
|
|
119
|
+
|
|
120
|
+
User request: "{{userMessage}}"
|
|
121
|
+
|
|
122
|
+
Extract and return ONLY a JSON object (no markdown formatting, no code blocks) with:
|
|
123
|
+
{
|
|
124
|
+
"issueId": "The issue identifier (e.g., ENG-123, COM2-7)"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
Return only the JSON object, no other text.`;
|
|
128
|
+
var getActivityTemplate = `Extract activity filter criteria from the user's request.
|
|
129
|
+
|
|
130
|
+
User request: "{{userMessage}}"
|
|
131
|
+
|
|
132
|
+
The user might ask for activity in various ways:
|
|
133
|
+
- "Show me today's activity" → time range filter
|
|
134
|
+
- "What issues were created?" → action type filter
|
|
135
|
+
- "What did John do yesterday?" → user filter + time range
|
|
136
|
+
- "Activity on ENG-123" → resource filter
|
|
137
|
+
- "Recent comment activity" → action type + recency
|
|
138
|
+
- "Failed operations this week" → success filter + time range
|
|
139
|
+
|
|
140
|
+
Return ONLY a JSON object:
|
|
141
|
+
{
|
|
142
|
+
"timeRange": {
|
|
143
|
+
"period": "today/yesterday/this-week/last-week/this-month",
|
|
144
|
+
"from": "ISO datetime if specific",
|
|
145
|
+
"to": "ISO datetime if specific"
|
|
473
146
|
},
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (!content) {
|
|
482
|
-
const errorMessage = "Please provide a description for the issue.";
|
|
483
|
-
await callback?.({
|
|
484
|
-
text: errorMessage,
|
|
485
|
-
source: message.content.source
|
|
486
|
-
});
|
|
487
|
-
return {
|
|
488
|
-
text: errorMessage,
|
|
489
|
-
success: false
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
const structuredData = _options?.issueData;
|
|
493
|
-
let issueData;
|
|
494
|
-
if (structuredData) {
|
|
495
|
-
issueData = structuredData;
|
|
496
|
-
} else {
|
|
497
|
-
const prompt = createIssueTemplate.replace("{{userMessage}}", content);
|
|
498
|
-
const response = await runtime.useModel(ModelType.TEXT_LARGE, {
|
|
499
|
-
prompt
|
|
500
|
-
});
|
|
501
|
-
if (!response) {
|
|
502
|
-
throw new Error("Failed to extract issue information");
|
|
503
|
-
}
|
|
504
|
-
try {
|
|
505
|
-
const cleanedResponse = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
|
|
506
|
-
const parsed = JSON.parse(cleanedResponse);
|
|
507
|
-
issueData = {
|
|
508
|
-
title: parsed.title || void 0,
|
|
509
|
-
description: parsed.description || void 0,
|
|
510
|
-
priority: parsed.priority ? Number(parsed.priority) : void 0
|
|
511
|
-
};
|
|
512
|
-
if (parsed.teamKey) {
|
|
513
|
-
const teams = await linearService.getTeams();
|
|
514
|
-
const team = teams.find(
|
|
515
|
-
(t) => t.key.toLowerCase() === parsed.teamKey.toLowerCase()
|
|
516
|
-
);
|
|
517
|
-
if (team) {
|
|
518
|
-
issueData.teamId = team.id;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
if (parsed.assignee && parsed.assignee !== "") {
|
|
522
|
-
const cleanAssignee = parsed.assignee.replace(/^@/, "");
|
|
523
|
-
const users = await linearService.getUsers();
|
|
524
|
-
const user = users.find(
|
|
525
|
-
(u) => u.email === cleanAssignee || u.name.toLowerCase().includes(cleanAssignee.toLowerCase())
|
|
526
|
-
);
|
|
527
|
-
if (user) {
|
|
528
|
-
issueData.assigneeId = user.id;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
if (parsed.labels && Array.isArray(parsed.labels) && parsed.labels.length > 0) {
|
|
532
|
-
const labels = await linearService.getLabels(issueData.teamId);
|
|
533
|
-
const labelIds = [];
|
|
534
|
-
for (const labelName of parsed.labels) {
|
|
535
|
-
if (labelName && labelName !== "") {
|
|
536
|
-
const label = labels.find(
|
|
537
|
-
(l) => l.name.toLowerCase() === labelName.toLowerCase()
|
|
538
|
-
);
|
|
539
|
-
if (label) {
|
|
540
|
-
labelIds.push(label.id);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
if (labelIds.length > 0) {
|
|
545
|
-
issueData.labelIds = labelIds;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
if (!issueData.teamId) {
|
|
549
|
-
const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
|
|
550
|
-
if (defaultTeamKey) {
|
|
551
|
-
const teams = await linearService.getTeams();
|
|
552
|
-
const defaultTeam = teams.find(
|
|
553
|
-
(t) => t.key.toLowerCase() === defaultTeamKey.toLowerCase()
|
|
554
|
-
);
|
|
555
|
-
if (defaultTeam) {
|
|
556
|
-
issueData.teamId = defaultTeam.id;
|
|
557
|
-
logger2.info(`Using configured default team: ${defaultTeam.name} (${defaultTeam.key})`);
|
|
558
|
-
} else {
|
|
559
|
-
logger2.warn(`Default team key ${defaultTeamKey} not found`);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
if (!issueData.teamId) {
|
|
563
|
-
const teams = await linearService.getTeams();
|
|
564
|
-
if (teams.length > 0) {
|
|
565
|
-
issueData.teamId = teams[0].id;
|
|
566
|
-
logger2.warn(`No team specified, using first available team: ${teams[0].name}`);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
} catch (parseError) {
|
|
571
|
-
logger2.error("Failed to parse LLM response:", parseError);
|
|
572
|
-
issueData = {
|
|
573
|
-
title: content.length > 100 ? content.substring(0, 100) + "..." : content,
|
|
574
|
-
description: content
|
|
575
|
-
};
|
|
576
|
-
const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
|
|
577
|
-
const teams = await linearService.getTeams();
|
|
578
|
-
if (defaultTeamKey) {
|
|
579
|
-
const defaultTeam = teams.find(
|
|
580
|
-
(t) => t.key.toLowerCase() === defaultTeamKey.toLowerCase()
|
|
581
|
-
);
|
|
582
|
-
if (defaultTeam) {
|
|
583
|
-
issueData.teamId = defaultTeam.id;
|
|
584
|
-
logger2.info(`Using configured default team for fallback: ${defaultTeam.name} (${defaultTeam.key})`);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
if (!issueData.teamId && teams.length > 0) {
|
|
588
|
-
issueData.teamId = teams[0].id;
|
|
589
|
-
logger2.warn(`Using first available team for fallback: ${teams[0].name}`);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
if (!issueData.title) {
|
|
594
|
-
const errorMessage = "Could not determine issue title. Please provide more details.";
|
|
595
|
-
await callback?.({
|
|
596
|
-
text: errorMessage,
|
|
597
|
-
source: message.content.source
|
|
598
|
-
});
|
|
599
|
-
return {
|
|
600
|
-
text: errorMessage,
|
|
601
|
-
success: false
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
if (!issueData.teamId) {
|
|
605
|
-
const errorMessage = "No Linear teams found. Please ensure at least one team exists in your Linear workspace.";
|
|
606
|
-
await callback?.({
|
|
607
|
-
text: errorMessage,
|
|
608
|
-
source: message.content.source
|
|
609
|
-
});
|
|
610
|
-
return {
|
|
611
|
-
text: errorMessage,
|
|
612
|
-
success: false
|
|
613
|
-
};
|
|
614
|
-
}
|
|
615
|
-
const issue = await linearService.createIssue(issueData);
|
|
616
|
-
const successMessage = `\u2705 Created Linear issue: ${issue.title} (${issue.identifier})
|
|
617
|
-
|
|
618
|
-
View it at: ${issue.url}`;
|
|
619
|
-
await callback?.({
|
|
620
|
-
text: successMessage,
|
|
621
|
-
source: message.content.source
|
|
622
|
-
});
|
|
623
|
-
return {
|
|
624
|
-
text: `Created issue: ${issue.title} (${issue.identifier})`,
|
|
625
|
-
success: true,
|
|
626
|
-
data: {
|
|
627
|
-
issueId: issue.id,
|
|
628
|
-
identifier: issue.identifier,
|
|
629
|
-
url: issue.url
|
|
630
|
-
}
|
|
631
|
-
};
|
|
632
|
-
} catch (error) {
|
|
633
|
-
logger2.error("Failed to create issue:", error);
|
|
634
|
-
const errorMessage = `\u274C Failed to create issue: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
635
|
-
await callback?.({
|
|
636
|
-
text: errorMessage,
|
|
637
|
-
source: message.content.source
|
|
638
|
-
});
|
|
639
|
-
return {
|
|
640
|
-
text: errorMessage,
|
|
641
|
-
success: false
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
};
|
|
147
|
+
"actionTypes": ["create_issue/update_issue/delete_issue/create_comment/search_issues/etc"],
|
|
148
|
+
"resourceTypes": ["issue/project/comment/team"],
|
|
149
|
+
"resourceId": "Specific resource ID if mentioned (e.g., ENG-123)",
|
|
150
|
+
"user": "User name or 'me' for current user",
|
|
151
|
+
"successFilter": "success/failed/all",
|
|
152
|
+
"limit": number (default 10)
|
|
153
|
+
}
|
|
646
154
|
|
|
647
|
-
|
|
648
|
-
import { logger as logger3, ModelType as ModelType2 } from "@elizaos/core";
|
|
155
|
+
Only include fields that are clearly mentioned.`;
|
|
649
156
|
var getIssueTemplate = `Extract issue identification from the user's request.
|
|
650
157
|
|
|
651
158
|
User request: "{{userMessage}}"
|
|
@@ -672,285 +179,95 @@ Return ONLY a JSON object:
|
|
|
672
179
|
}
|
|
673
180
|
|
|
674
181
|
Only include fields that are clearly mentioned or implied.`;
|
|
675
|
-
var
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
699
|
-
},
|
|
700
|
-
{
|
|
701
|
-
name: "Assistant",
|
|
702
|
-
content: {
|
|
703
|
-
text: "Let me find the login bug issue for you.",
|
|
704
|
-
actions: ["GET_LINEAR_ISSUE"]
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
], [
|
|
708
|
-
{
|
|
709
|
-
name: "User",
|
|
710
|
-
content: {
|
|
711
|
-
text: "Show me the latest high priority issue assigned to Sarah"
|
|
712
|
-
}
|
|
713
|
-
},
|
|
714
|
-
{
|
|
715
|
-
name: "Assistant",
|
|
716
|
-
content: {
|
|
717
|
-
text: "I'll find the latest high priority issue assigned to Sarah.",
|
|
718
|
-
actions: ["GET_LINEAR_ISSUE"]
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
]],
|
|
722
|
-
async validate(runtime, _message, _state) {
|
|
723
|
-
try {
|
|
724
|
-
const apiKey = runtime.getSetting("LINEAR_API_KEY");
|
|
725
|
-
return !!apiKey;
|
|
726
|
-
} catch {
|
|
727
|
-
return false;
|
|
728
|
-
}
|
|
182
|
+
var listProjectsTemplate = `Extract project filter criteria from the user's request.
|
|
183
|
+
|
|
184
|
+
User request: "{{userMessage}}"
|
|
185
|
+
|
|
186
|
+
The user might ask for projects in various ways:
|
|
187
|
+
- "Show me all projects" → list all projects
|
|
188
|
+
- "Active projects" → filter by state (active/planned/completed)
|
|
189
|
+
- "Projects due this quarter" → filter by target date
|
|
190
|
+
- "Which projects is Sarah managing?" → filter by lead/owner
|
|
191
|
+
- "Projects with high priority issues" → filter by contained issue priority
|
|
192
|
+
- "Projects for the engineering team" → filter by team
|
|
193
|
+
- "Completed projects" → filter by state
|
|
194
|
+
- "Projects starting next month" → filter by start date
|
|
195
|
+
|
|
196
|
+
Return ONLY a JSON object:
|
|
197
|
+
{
|
|
198
|
+
"teamFilter": "Team name or key if mentioned",
|
|
199
|
+
"stateFilter": "active/planned/completed/all",
|
|
200
|
+
"dateFilter": {
|
|
201
|
+
"type": "due/starting",
|
|
202
|
+
"period": "this-week/this-month/this-quarter/next-month/next-quarter",
|
|
203
|
+
"from": "ISO date if specific",
|
|
204
|
+
"to": "ISO date if specific"
|
|
729
205
|
},
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
if (!linearService) {
|
|
734
|
-
throw new Error("Linear service not available");
|
|
735
|
-
}
|
|
736
|
-
const content = message.content.text;
|
|
737
|
-
if (!content) {
|
|
738
|
-
const errorMessage2 = "Please specify which issue you want to see.";
|
|
739
|
-
await callback?.({
|
|
740
|
-
text: errorMessage2,
|
|
741
|
-
source: message.content.source
|
|
742
|
-
});
|
|
743
|
-
return {
|
|
744
|
-
text: errorMessage2,
|
|
745
|
-
success: false
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
const prompt = getIssueTemplate.replace("{{userMessage}}", content);
|
|
749
|
-
const response = await runtime.useModel(ModelType2.TEXT_LARGE, {
|
|
750
|
-
prompt
|
|
751
|
-
});
|
|
752
|
-
if (!response) {
|
|
753
|
-
const issueMatch = content.match(/(\w+-\d+)/);
|
|
754
|
-
if (issueMatch) {
|
|
755
|
-
const issue = await linearService.getIssue(issueMatch[1]);
|
|
756
|
-
return await formatIssueResponse(issue, callback, message);
|
|
757
|
-
}
|
|
758
|
-
throw new Error("Could not understand issue reference");
|
|
759
|
-
}
|
|
760
|
-
try {
|
|
761
|
-
const parsed = JSON.parse(response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim());
|
|
762
|
-
if (parsed.directId) {
|
|
763
|
-
const issue = await linearService.getIssue(parsed.directId);
|
|
764
|
-
return await formatIssueResponse(issue, callback, message);
|
|
765
|
-
}
|
|
766
|
-
if (parsed.searchBy && Object.keys(parsed.searchBy).length > 0) {
|
|
767
|
-
const filters = {};
|
|
768
|
-
if (parsed.searchBy.title) {
|
|
769
|
-
filters.query = parsed.searchBy.title;
|
|
770
|
-
}
|
|
771
|
-
if (parsed.searchBy.assignee) {
|
|
772
|
-
filters.assignee = [parsed.searchBy.assignee];
|
|
773
|
-
}
|
|
774
|
-
if (parsed.searchBy.priority) {
|
|
775
|
-
const priorityMap = {
|
|
776
|
-
"urgent": 1,
|
|
777
|
-
"high": 2,
|
|
778
|
-
"normal": 3,
|
|
779
|
-
"low": 4,
|
|
780
|
-
"1": 1,
|
|
781
|
-
"2": 2,
|
|
782
|
-
"3": 3,
|
|
783
|
-
"4": 4
|
|
784
|
-
};
|
|
785
|
-
const priority = priorityMap[parsed.searchBy.priority.toLowerCase()];
|
|
786
|
-
if (priority) {
|
|
787
|
-
filters.priority = [priority];
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
if (parsed.searchBy.team) {
|
|
791
|
-
filters.team = parsed.searchBy.team;
|
|
792
|
-
}
|
|
793
|
-
if (parsed.searchBy.state) {
|
|
794
|
-
filters.state = [parsed.searchBy.state];
|
|
795
|
-
}
|
|
796
|
-
const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
|
|
797
|
-
if (defaultTeamKey && !filters.team) {
|
|
798
|
-
filters.team = defaultTeamKey;
|
|
799
|
-
}
|
|
800
|
-
const issues = await linearService.searchIssues({
|
|
801
|
-
...filters,
|
|
802
|
-
limit: parsed.searchBy.recency ? 10 : 5
|
|
803
|
-
});
|
|
804
|
-
if (issues.length === 0) {
|
|
805
|
-
const noResultsMessage = "No issues found matching your criteria.";
|
|
806
|
-
await callback?.({
|
|
807
|
-
text: noResultsMessage,
|
|
808
|
-
source: message.content.source
|
|
809
|
-
});
|
|
810
|
-
return {
|
|
811
|
-
text: noResultsMessage,
|
|
812
|
-
success: false
|
|
813
|
-
};
|
|
814
|
-
}
|
|
815
|
-
if (parsed.searchBy.recency) {
|
|
816
|
-
issues.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
817
|
-
}
|
|
818
|
-
if (parsed.searchBy.recency && issues.length > 0) {
|
|
819
|
-
return await formatIssueResponse(issues[0], callback, message);
|
|
820
|
-
}
|
|
821
|
-
if (issues.length === 1) {
|
|
822
|
-
return await formatIssueResponse(issues[0], callback, message);
|
|
823
|
-
}
|
|
824
|
-
const issueList = await Promise.all(issues.slice(0, 5).map(async (issue, index) => {
|
|
825
|
-
const state = await issue.state;
|
|
826
|
-
return `${index + 1}. ${issue.identifier}: ${issue.title} (${state?.name || "No state"})`;
|
|
827
|
-
}));
|
|
828
|
-
const clarifyMessage = `Found ${issues.length} issues matching your criteria:
|
|
829
|
-
${issueList.join("\n")}
|
|
206
|
+
"leadFilter": "Project lead name if mentioned",
|
|
207
|
+
"showAll": true/false (true if user explicitly asks for "all")
|
|
208
|
+
}
|
|
830
209
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
text: clarifyMessage,
|
|
834
|
-
source: message.content.source
|
|
835
|
-
});
|
|
836
|
-
return {
|
|
837
|
-
text: clarifyMessage,
|
|
838
|
-
success: true,
|
|
839
|
-
data: {
|
|
840
|
-
multipleResults: true,
|
|
841
|
-
issues: issues.slice(0, 5).map((i) => ({
|
|
842
|
-
id: i.id,
|
|
843
|
-
identifier: i.identifier,
|
|
844
|
-
title: i.title
|
|
845
|
-
}))
|
|
846
|
-
}
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
} catch (parseError) {
|
|
850
|
-
logger3.warn("Failed to parse LLM response, falling back to regex:", parseError);
|
|
851
|
-
const issueMatch = content.match(/(\w+-\d+)/);
|
|
852
|
-
if (issueMatch) {
|
|
853
|
-
const issue = await linearService.getIssue(issueMatch[1]);
|
|
854
|
-
return await formatIssueResponse(issue, callback, message);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
const errorMessage = "Could not understand which issue you want to see. Please provide an issue ID (e.g., ENG-123) or describe it more specifically.";
|
|
858
|
-
await callback?.({
|
|
859
|
-
text: errorMessage,
|
|
860
|
-
source: message.content.source
|
|
861
|
-
});
|
|
862
|
-
return {
|
|
863
|
-
text: errorMessage,
|
|
864
|
-
success: false
|
|
865
|
-
};
|
|
866
|
-
} catch (error) {
|
|
867
|
-
logger3.error("Failed to get issue:", error);
|
|
868
|
-
const errorMessage = `\u274C Failed to get issue: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
869
|
-
await callback?.({
|
|
870
|
-
text: errorMessage,
|
|
871
|
-
source: message.content.source
|
|
872
|
-
});
|
|
873
|
-
return {
|
|
874
|
-
text: errorMessage,
|
|
875
|
-
success: false
|
|
876
|
-
};
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
};
|
|
880
|
-
async function formatIssueResponse(issue, callback, message) {
|
|
881
|
-
const assignee = await issue.assignee;
|
|
882
|
-
const state = await issue.state;
|
|
883
|
-
const team = await issue.team;
|
|
884
|
-
const labels = await issue.labels();
|
|
885
|
-
const project = await issue.project;
|
|
886
|
-
const issueDetails = {
|
|
887
|
-
id: issue.id,
|
|
888
|
-
identifier: issue.identifier,
|
|
889
|
-
title: issue.title,
|
|
890
|
-
description: issue.description,
|
|
891
|
-
priority: issue.priority,
|
|
892
|
-
priorityLabel: issue.priorityLabel,
|
|
893
|
-
url: issue.url,
|
|
894
|
-
createdAt: issue.createdAt,
|
|
895
|
-
updatedAt: issue.updatedAt,
|
|
896
|
-
dueDate: issue.dueDate,
|
|
897
|
-
estimate: issue.estimate,
|
|
898
|
-
assignee: assignee ? {
|
|
899
|
-
id: assignee.id,
|
|
900
|
-
name: assignee.name,
|
|
901
|
-
email: assignee.email
|
|
902
|
-
} : null,
|
|
903
|
-
state: state ? {
|
|
904
|
-
id: state.id,
|
|
905
|
-
name: state.name,
|
|
906
|
-
type: state.type,
|
|
907
|
-
color: state.color
|
|
908
|
-
} : null,
|
|
909
|
-
team: team ? {
|
|
910
|
-
id: team.id,
|
|
911
|
-
name: team.name,
|
|
912
|
-
key: team.key
|
|
913
|
-
} : null,
|
|
914
|
-
labels: labels.nodes.map((label) => ({
|
|
915
|
-
id: label.id,
|
|
916
|
-
name: label.name,
|
|
917
|
-
color: label.color
|
|
918
|
-
})),
|
|
919
|
-
project: project ? {
|
|
920
|
-
id: project.id,
|
|
921
|
-
name: project.name,
|
|
922
|
-
description: project.description
|
|
923
|
-
} : null
|
|
924
|
-
};
|
|
925
|
-
const priorityLabels = ["", "Urgent", "High", "Normal", "Low"];
|
|
926
|
-
const priority = priorityLabels[issue.priority || 0] || "No priority";
|
|
927
|
-
const labelText = issueDetails.labels.length > 0 ? `Labels: ${issueDetails.labels.map((l) => l.name).join(", ")}` : "";
|
|
928
|
-
const issueMessage = `\u{1F4CB} **${issue.identifier}: ${issue.title}**
|
|
929
|
-
|
|
930
|
-
Status: ${state?.name || "No status"}
|
|
931
|
-
Priority: ${priority}
|
|
932
|
-
Team: ${team?.name || "No team"}
|
|
933
|
-
Assignee: ${assignee?.name || "Unassigned"}
|
|
934
|
-
${issue.dueDate ? `Due: ${new Date(issue.dueDate).toLocaleDateString()}` : ""}
|
|
935
|
-
${labelText}
|
|
936
|
-
${project ? `Project: ${project.name}` : ""}
|
|
210
|
+
Only include fields that are clearly mentioned.`;
|
|
211
|
+
var listTeamsTemplate = `Extract team filter criteria from the user's request.
|
|
937
212
|
|
|
938
|
-
|
|
213
|
+
User request: "{{userMessage}}"
|
|
939
214
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
215
|
+
The user might ask for teams in various ways:
|
|
216
|
+
- "Show me all teams" → list all teams
|
|
217
|
+
- "Engineering teams" → filter by teams with engineering in name/description
|
|
218
|
+
- "List teams I'm part of" → filter by membership
|
|
219
|
+
- "Which teams work on the mobile app?" → filter by description/focus
|
|
220
|
+
- "Show me the ELIZA team details" → specific team lookup
|
|
221
|
+
- "Active teams" → teams with recent activity
|
|
222
|
+
- "Frontend and backend teams" → multiple team types
|
|
223
|
+
|
|
224
|
+
Return ONLY a JSON object:
|
|
225
|
+
{
|
|
226
|
+
"nameFilter": "Keywords to search in team names",
|
|
227
|
+
"specificTeam": "Specific team name or key if looking for one team",
|
|
228
|
+
"myTeams": true/false (true if user wants their teams),
|
|
229
|
+
"showAll": true/false (true if user explicitly asks for "all"),
|
|
230
|
+
"includeDetails": true/false (true if user wants detailed info)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
Only include fields that are clearly mentioned.`;
|
|
234
|
+
var searchIssuesTemplate = `Extract search criteria from the user's request for Linear issues.
|
|
235
|
+
|
|
236
|
+
User request: "{{userMessage}}"
|
|
237
|
+
|
|
238
|
+
The user might express searches in various ways:
|
|
239
|
+
- "Show me what John is working on" → assignee filter
|
|
240
|
+
- "Any blockers for the next release?" → priority/label filters
|
|
241
|
+
- "Issues created this week" → date range filter
|
|
242
|
+
- "My high priority bugs" → assignee (current user) + priority + label
|
|
243
|
+
- "Unassigned tasks in the backend team" → no assignee + team filter
|
|
244
|
+
- "What did Sarah close yesterday?" → assignee + state + date
|
|
245
|
+
- "Bugs that are almost done" → label + state filter
|
|
246
|
+
- "Show me the oldest open issues" → state + sort order
|
|
247
|
+
|
|
248
|
+
Extract and return ONLY a JSON object:
|
|
249
|
+
{
|
|
250
|
+
"query": "General search text for title/description",
|
|
251
|
+
"states": ["state names like In Progress, Done, Todo, Backlog"],
|
|
252
|
+
"assignees": ["assignee names or emails, or 'me' for current user"],
|
|
253
|
+
"priorities": ["urgent/high/normal/low or 1/2/3/4"],
|
|
254
|
+
"teams": ["team names or keys"],
|
|
255
|
+
"labels": ["label names"],
|
|
256
|
+
"hasAssignee": true/false (true = has assignee, false = unassigned),
|
|
257
|
+
"dateRange": {
|
|
258
|
+
"field": "created/updated/completed",
|
|
259
|
+
"period": "today/yesterday/this-week/last-week/this-month/last-month",
|
|
260
|
+
"from": "ISO date if specific date",
|
|
261
|
+
"to": "ISO date if specific date"
|
|
262
|
+
},
|
|
263
|
+
"sort": {
|
|
264
|
+
"field": "created/updated/priority",
|
|
265
|
+
"order": "asc/desc"
|
|
266
|
+
},
|
|
267
|
+
"limit": number (default 10)
|
|
950
268
|
}
|
|
951
269
|
|
|
952
|
-
|
|
953
|
-
import { logger as logger4, ModelType as ModelType3 } from "@elizaos/core";
|
|
270
|
+
Only include fields that are clearly mentioned or implied. For "my" issues, set assignees to ["me"].`;
|
|
954
271
|
var updateIssueTemplate = `Given the user's request to update a Linear issue, extract the information needed.
|
|
955
272
|
|
|
956
273
|
User request: "{{userMessage}}"
|
|
@@ -970,60 +287,67 @@ Extract and return ONLY a JSON object (no markdown formatting, no code blocks) w
|
|
|
970
287
|
}
|
|
971
288
|
|
|
972
289
|
Only include fields that are being updated. Return only the JSON object, no other text.`;
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
290
|
+
|
|
291
|
+
// src/actions/createComment.ts
|
|
292
|
+
var createCommentAction = {
|
|
293
|
+
name: "CREATE_LINEAR_COMMENT",
|
|
294
|
+
description: "Add a comment to a Linear issue",
|
|
295
|
+
similes: [
|
|
296
|
+
"create-linear-comment",
|
|
297
|
+
"add-linear-comment",
|
|
298
|
+
"comment-on-linear-issue",
|
|
299
|
+
"reply-to-linear-issue"
|
|
300
|
+
],
|
|
301
|
+
examples: [
|
|
302
|
+
[
|
|
303
|
+
{
|
|
304
|
+
name: "User",
|
|
305
|
+
content: {
|
|
306
|
+
text: "Comment on ENG-123: This looks good to me"
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: "Assistant",
|
|
311
|
+
content: {
|
|
312
|
+
text: "I'll add your comment to issue ENG-123.",
|
|
313
|
+
actions: ["CREATE_LINEAR_COMMENT"]
|
|
314
|
+
}
|
|
989
315
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
316
|
+
],
|
|
317
|
+
[
|
|
318
|
+
{
|
|
319
|
+
name: "User",
|
|
320
|
+
content: {
|
|
321
|
+
text: "Tell the login bug that we need more information from QA"
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: "Assistant",
|
|
326
|
+
content: {
|
|
327
|
+
text: "I'll add that comment to the login bug issue.",
|
|
328
|
+
actions: ["CREATE_LINEAR_COMMENT"]
|
|
329
|
+
}
|
|
1003
330
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
331
|
+
],
|
|
332
|
+
[
|
|
333
|
+
{
|
|
334
|
+
name: "User",
|
|
335
|
+
content: {
|
|
336
|
+
text: "Reply to COM2-7: Thanks for the update, I'll look into it"
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: "Assistant",
|
|
341
|
+
content: {
|
|
342
|
+
text: "I'll add your reply to issue COM2-7.",
|
|
343
|
+
actions: ["CREATE_LINEAR_COMMENT"]
|
|
344
|
+
}
|
|
1017
345
|
}
|
|
1018
|
-
|
|
1019
|
-
]
|
|
346
|
+
]
|
|
347
|
+
],
|
|
1020
348
|
async validate(runtime, _message, _state) {
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
return !!apiKey;
|
|
1024
|
-
} catch {
|
|
1025
|
-
return false;
|
|
1026
|
-
}
|
|
349
|
+
const apiKey = runtime.getSetting("LINEAR_API_KEY");
|
|
350
|
+
return !!apiKey;
|
|
1027
351
|
},
|
|
1028
352
|
async handler(runtime, message, _state, _options, callback) {
|
|
1029
353
|
try {
|
|
@@ -1033,7 +357,7 @@ var updateIssueAction = {
|
|
|
1033
357
|
}
|
|
1034
358
|
const content = message.content.text;
|
|
1035
359
|
if (!content) {
|
|
1036
|
-
const errorMessage = "Please provide
|
|
360
|
+
const errorMessage = "Please provide a message with the issue and comment content.";
|
|
1037
361
|
await callback?.({
|
|
1038
362
|
text: errorMessage,
|
|
1039
363
|
source: message.content.source
|
|
@@ -1043,126 +367,312 @@ var updateIssueAction = {
|
|
|
1043
367
|
success: false
|
|
1044
368
|
};
|
|
1045
369
|
}
|
|
1046
|
-
const prompt = updateIssueTemplate.replace("{{userMessage}}", content);
|
|
1047
|
-
const response = await runtime.useModel(ModelType3.TEXT_LARGE, {
|
|
1048
|
-
prompt
|
|
1049
|
-
});
|
|
1050
|
-
if (!response) {
|
|
1051
|
-
throw new Error("Failed to extract update information");
|
|
1052
|
-
}
|
|
1053
370
|
let issueId;
|
|
1054
|
-
let
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
371
|
+
let commentBody;
|
|
372
|
+
const params = _options?.parameters;
|
|
373
|
+
if (params?.issueId && params?.body) {
|
|
374
|
+
issueId = params.issueId;
|
|
375
|
+
commentBody = params.body;
|
|
376
|
+
} else {
|
|
377
|
+
const prompt = createCommentTemplate.replace("{{userMessage}}", content);
|
|
378
|
+
const response = await runtime.useModel(ModelType.TEXT_LARGE, {
|
|
379
|
+
prompt
|
|
380
|
+
});
|
|
381
|
+
if (!response) {
|
|
382
|
+
const issueMatch = content.match(/(?:comment on|add.*comment.*to|reply to|tell)\s+(\w+-\d+):?\s*(.*)/i);
|
|
383
|
+
if (issueMatch) {
|
|
384
|
+
issueId = issueMatch[1];
|
|
385
|
+
commentBody = issueMatch[2].trim();
|
|
386
|
+
} else {
|
|
387
|
+
throw new Error("Could not understand comment request");
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
try {
|
|
391
|
+
const parsed = JSON.parse(response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim());
|
|
392
|
+
if (parsed.issueId) {
|
|
393
|
+
issueId = parsed.issueId;
|
|
394
|
+
commentBody = parsed.commentBody;
|
|
395
|
+
} else if (parsed.issueDescription) {
|
|
396
|
+
const filters = {
|
|
397
|
+
query: parsed.issueDescription,
|
|
398
|
+
limit: 5
|
|
399
|
+
};
|
|
400
|
+
const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
|
|
401
|
+
if (defaultTeamKey) {
|
|
402
|
+
filters.team = defaultTeamKey;
|
|
403
|
+
}
|
|
404
|
+
const issues = await linearService.searchIssues(filters);
|
|
405
|
+
if (issues.length === 0) {
|
|
406
|
+
const errorMessage = `No issues found matching "${parsed.issueDescription}". Please provide a specific issue ID.`;
|
|
407
|
+
await callback?.({
|
|
408
|
+
text: errorMessage,
|
|
409
|
+
source: message.content.source
|
|
410
|
+
});
|
|
411
|
+
return {
|
|
412
|
+
text: errorMessage,
|
|
413
|
+
success: false
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
if (issues.length === 1) {
|
|
417
|
+
issueId = issues[0].identifier;
|
|
418
|
+
commentBody = parsed.commentBody;
|
|
419
|
+
} else {
|
|
420
|
+
const issueList = await Promise.all(issues.map(async (issue2, index) => {
|
|
421
|
+
const state = await issue2.state;
|
|
422
|
+
return `${index + 1}. ${issue2.identifier}: ${issue2.title} (${state?.name || "No state"})`;
|
|
423
|
+
}));
|
|
424
|
+
const clarifyMessage = `Found multiple issues matching "${parsed.issueDescription}":
|
|
425
|
+
${issueList.join(`
|
|
426
|
+
`)}
|
|
427
|
+
|
|
428
|
+
Please specify which issue to comment on by its ID.`;
|
|
429
|
+
await callback?.({
|
|
430
|
+
text: clarifyMessage,
|
|
431
|
+
source: message.content.source
|
|
432
|
+
});
|
|
433
|
+
return {
|
|
434
|
+
text: clarifyMessage,
|
|
435
|
+
success: false,
|
|
436
|
+
data: {
|
|
437
|
+
multipleMatches: true,
|
|
438
|
+
issues: issues.map((i) => ({
|
|
439
|
+
id: i.id,
|
|
440
|
+
identifier: i.identifier,
|
|
441
|
+
title: i.title
|
|
442
|
+
})),
|
|
443
|
+
pendingComment: parsed.commentBody
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
} else {
|
|
448
|
+
throw new Error("No issue identifier or description found");
|
|
449
|
+
}
|
|
450
|
+
if (parsed.commentType && parsed.commentType !== "note") {
|
|
451
|
+
commentBody = `[${parsed.commentType.toUpperCase()}] ${commentBody}`;
|
|
452
|
+
}
|
|
453
|
+
} catch (parseError) {
|
|
454
|
+
logger2.warn("Failed to parse LLM response, falling back to regex:", parseError);
|
|
455
|
+
const issueMatch = content.match(/(?:comment on|add.*comment.*to|reply to|tell)\s+(\w+-\d+):?\s*(.*)/i);
|
|
456
|
+
if (!issueMatch) {
|
|
457
|
+
const errorMessage = 'Please specify the issue ID and comment content. Example: "Comment on ENG-123: This looks good"';
|
|
458
|
+
await callback?.({
|
|
459
|
+
text: errorMessage,
|
|
460
|
+
source: message.content.source
|
|
461
|
+
});
|
|
462
|
+
return {
|
|
463
|
+
text: errorMessage,
|
|
464
|
+
success: false
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
issueId = issueMatch[1];
|
|
468
|
+
commentBody = issueMatch[2].trim();
|
|
469
|
+
}
|
|
1061
470
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
471
|
+
}
|
|
472
|
+
if (!commentBody || commentBody.length === 0) {
|
|
473
|
+
const errorMessage = "Please provide the comment content.";
|
|
474
|
+
await callback?.({
|
|
475
|
+
text: errorMessage,
|
|
476
|
+
source: message.content.source
|
|
477
|
+
});
|
|
478
|
+
return {
|
|
479
|
+
text: errorMessage,
|
|
480
|
+
success: false
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
const issue = await linearService.getIssue(issueId);
|
|
484
|
+
const comment = await linearService.createComment({
|
|
485
|
+
issueId: issue.id,
|
|
486
|
+
body: commentBody
|
|
487
|
+
});
|
|
488
|
+
const successMessage = `✅ Comment added to issue ${issue.identifier}: "${commentBody}"`;
|
|
489
|
+
await callback?.({
|
|
490
|
+
text: successMessage,
|
|
491
|
+
source: message.content.source
|
|
492
|
+
});
|
|
493
|
+
return {
|
|
494
|
+
text: `Added comment to issue ${issue.identifier}`,
|
|
495
|
+
success: true,
|
|
496
|
+
data: {
|
|
497
|
+
commentId: comment.id,
|
|
498
|
+
issueId: issue.id,
|
|
499
|
+
issueIdentifier: issue.identifier,
|
|
500
|
+
commentBody,
|
|
501
|
+
createdAt: comment.createdAt instanceof Date ? comment.createdAt.toISOString() : comment.createdAt
|
|
1064
502
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
503
|
+
};
|
|
504
|
+
} catch (error) {
|
|
505
|
+
logger2.error("Failed to create comment:", error);
|
|
506
|
+
const errorMessage = `❌ Failed to create comment: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
507
|
+
await callback?.({
|
|
508
|
+
text: errorMessage,
|
|
509
|
+
source: message.content.source
|
|
510
|
+
});
|
|
511
|
+
return {
|
|
512
|
+
text: errorMessage,
|
|
513
|
+
success: false
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
// src/actions/createIssue.ts
|
|
520
|
+
import {
|
|
521
|
+
logger as logger3,
|
|
522
|
+
ModelType as ModelType2
|
|
523
|
+
} from "@elizaos/core";
|
|
524
|
+
var createIssueAction = {
|
|
525
|
+
name: "CREATE_LINEAR_ISSUE",
|
|
526
|
+
description: "Create a new issue in Linear",
|
|
527
|
+
similes: ["create-linear-issue", "new-linear-issue", "add-linear-issue"],
|
|
528
|
+
examples: [
|
|
529
|
+
[
|
|
530
|
+
{
|
|
531
|
+
name: "User",
|
|
532
|
+
content: {
|
|
533
|
+
text: "Create a new issue: Fix login button not working on mobile devices"
|
|
1067
534
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
name: "Assistant",
|
|
538
|
+
content: {
|
|
539
|
+
text: "I'll create that issue for you in Linear.",
|
|
540
|
+
actions: ["CREATE_LINEAR_ISSUE"]
|
|
1070
541
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
logger4.info(`Moving issue to team: ${team.name} (${team.key})`);
|
|
1079
|
-
} else {
|
|
1080
|
-
logger4.warn(`Team with key ${parsed.updates.teamKey} not found`);
|
|
1081
|
-
}
|
|
542
|
+
}
|
|
543
|
+
],
|
|
544
|
+
[
|
|
545
|
+
{
|
|
546
|
+
name: "User",
|
|
547
|
+
content: {
|
|
548
|
+
text: "Create a bug report for the ENG team: API returns 500 error when updating user profile"
|
|
1082
549
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
name: "Assistant",
|
|
553
|
+
content: {
|
|
554
|
+
text: "I'll create a bug report for the engineering team right away.",
|
|
555
|
+
actions: ["CREATE_LINEAR_ISSUE"]
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
]
|
|
559
|
+
],
|
|
560
|
+
async validate(runtime, _message, _state) {
|
|
561
|
+
const apiKey = runtime.getSetting("LINEAR_API_KEY");
|
|
562
|
+
return !!apiKey;
|
|
563
|
+
},
|
|
564
|
+
async handler(runtime, message, _state, _options, callback) {
|
|
565
|
+
try {
|
|
566
|
+
const linearService = runtime.getService("linear");
|
|
567
|
+
if (!linearService) {
|
|
568
|
+
throw new Error("Linear service not available");
|
|
569
|
+
}
|
|
570
|
+
const content = message.content.text;
|
|
571
|
+
if (!content) {
|
|
572
|
+
const errorMessage = "Please provide a description for the issue.";
|
|
573
|
+
await callback?.({
|
|
574
|
+
text: errorMessage,
|
|
575
|
+
source: message.content.source
|
|
576
|
+
});
|
|
577
|
+
return {
|
|
578
|
+
text: errorMessage,
|
|
579
|
+
success: false
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
const params = _options?.parameters;
|
|
583
|
+
const structuredData = params?.issueData;
|
|
584
|
+
let issueData;
|
|
585
|
+
if (structuredData) {
|
|
586
|
+
issueData = structuredData;
|
|
587
|
+
} else {
|
|
588
|
+
const prompt = createIssueTemplate.replace("{{userMessage}}", content);
|
|
589
|
+
const response = await runtime.useModel(ModelType2.TEXT_LARGE, {
|
|
590
|
+
prompt
|
|
591
|
+
});
|
|
592
|
+
if (!response) {
|
|
593
|
+
throw new Error("Failed to extract issue information");
|
|
594
|
+
}
|
|
595
|
+
try {
|
|
596
|
+
const cleanedResponse = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
|
|
597
|
+
const parsed = JSON.parse(cleanedResponse);
|
|
598
|
+
issueData = {
|
|
599
|
+
title: parsed.title || undefined,
|
|
600
|
+
description: parsed.description || undefined,
|
|
601
|
+
priority: parsed.priority ? Number(parsed.priority) : undefined
|
|
602
|
+
};
|
|
603
|
+
if (parsed.teamKey) {
|
|
604
|
+
const teams = await linearService.getTeams();
|
|
605
|
+
const team = teams.find((t) => t.key.toLowerCase() === parsed.teamKey.toLowerCase());
|
|
606
|
+
if (team) {
|
|
607
|
+
issueData.teamId = team.id;
|
|
608
|
+
}
|
|
1093
609
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
logger4.warn("Could not determine team for status update");
|
|
1101
|
-
} else {
|
|
1102
|
-
const states = await linearService.getWorkflowStates(teamId);
|
|
1103
|
-
const state = states.find(
|
|
1104
|
-
(s) => s.name.toLowerCase() === parsed.updates.status.toLowerCase() || s.type.toLowerCase() === parsed.updates.status.toLowerCase()
|
|
1105
|
-
);
|
|
1106
|
-
if (state) {
|
|
1107
|
-
updates.stateId = state.id;
|
|
1108
|
-
logger4.info(`Changing status to: ${state.name}`);
|
|
1109
|
-
} else {
|
|
1110
|
-
logger4.warn(`Status ${parsed.updates.status} not found for team`);
|
|
610
|
+
if (parsed.assignee && parsed.assignee !== "") {
|
|
611
|
+
const cleanAssignee = parsed.assignee.replace(/^@/, "");
|
|
612
|
+
const users = await linearService.getUsers();
|
|
613
|
+
const user = users.find((u) => u.email === cleanAssignee || u.name.toLowerCase().includes(cleanAssignee.toLowerCase()));
|
|
614
|
+
if (user) {
|
|
615
|
+
issueData.assigneeId = user.id;
|
|
1111
616
|
}
|
|
1112
617
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
);
|
|
1123
|
-
if (label) {
|
|
1124
|
-
labelIds.push(label.id);
|
|
618
|
+
if (parsed.labels && Array.isArray(parsed.labels) && parsed.labels.length > 0) {
|
|
619
|
+
const labels = await linearService.getLabels(issueData.teamId);
|
|
620
|
+
const labelIds = [];
|
|
621
|
+
for (const labelName of parsed.labels) {
|
|
622
|
+
if (labelName && labelName !== "") {
|
|
623
|
+
const label = labels.find((l) => l.name.toLowerCase() === labelName.toLowerCase());
|
|
624
|
+
if (label) {
|
|
625
|
+
labelIds.push(label.id);
|
|
626
|
+
}
|
|
1125
627
|
}
|
|
1126
628
|
}
|
|
629
|
+
if (labelIds.length > 0) {
|
|
630
|
+
issueData.labelIds = labelIds;
|
|
631
|
+
}
|
|
1127
632
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
"high": 2,
|
|
1154
|
-
"normal": 3,
|
|
1155
|
-
"medium": 3,
|
|
1156
|
-
"low": 4
|
|
633
|
+
if (!issueData.teamId) {
|
|
634
|
+
const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
|
|
635
|
+
if (defaultTeamKey) {
|
|
636
|
+
const teams = await linearService.getTeams();
|
|
637
|
+
const defaultTeam = teams.find((t) => t.key.toLowerCase() === defaultTeamKey.toLowerCase());
|
|
638
|
+
if (defaultTeam) {
|
|
639
|
+
issueData.teamId = defaultTeam.id;
|
|
640
|
+
logger3.info(`Using configured default team: ${defaultTeam.name} (${defaultTeam.key})`);
|
|
641
|
+
} else {
|
|
642
|
+
logger3.warn(`Default team key ${defaultTeamKey} not found`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (!issueData.teamId) {
|
|
646
|
+
const teams = await linearService.getTeams();
|
|
647
|
+
if (teams.length > 0) {
|
|
648
|
+
issueData.teamId = teams[0].id;
|
|
649
|
+
logger3.warn(`No team specified, using first available team: ${teams[0].name}`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
} catch (parseError) {
|
|
654
|
+
logger3.error("Failed to parse LLM response:", parseError);
|
|
655
|
+
issueData = {
|
|
656
|
+
title: content.length > 100 ? `${content.substring(0, 100)}...` : content,
|
|
657
|
+
description: content
|
|
1157
658
|
};
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
|
|
659
|
+
const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
|
|
660
|
+
const teams = await linearService.getTeams();
|
|
661
|
+
if (defaultTeamKey) {
|
|
662
|
+
const defaultTeam = teams.find((t) => t.key.toLowerCase() === defaultTeamKey.toLowerCase());
|
|
663
|
+
if (defaultTeam) {
|
|
664
|
+
issueData.teamId = defaultTeam.id;
|
|
665
|
+
logger3.info(`Using configured default team for fallback: ${defaultTeam.name} (${defaultTeam.key})`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
if (!issueData.teamId && teams.length > 0) {
|
|
669
|
+
issueData.teamId = teams[0].id;
|
|
670
|
+
logger3.warn(`Using first available team for fallback: ${teams[0].name}`);
|
|
1161
671
|
}
|
|
1162
672
|
}
|
|
1163
673
|
}
|
|
1164
|
-
if (
|
|
1165
|
-
const errorMessage =
|
|
674
|
+
if (!issueData.title) {
|
|
675
|
+
const errorMessage = "Could not determine issue title. Please provide more details.";
|
|
1166
676
|
await callback?.({
|
|
1167
677
|
text: errorMessage,
|
|
1168
678
|
source: message.content.source
|
|
@@ -1172,34 +682,37 @@ var updateIssueAction = {
|
|
|
1172
682
|
success: false
|
|
1173
683
|
};
|
|
1174
684
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
685
|
+
if (!issueData.teamId) {
|
|
686
|
+
const errorMessage = "No Linear teams found. Please ensure at least one team exists in your Linear workspace.";
|
|
687
|
+
await callback?.({
|
|
688
|
+
text: errorMessage,
|
|
689
|
+
source: message.content.source
|
|
690
|
+
});
|
|
691
|
+
return {
|
|
692
|
+
text: errorMessage,
|
|
693
|
+
success: false
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
const issue = await linearService.createIssue(issueData);
|
|
697
|
+
const successMessage = `✅ Created Linear issue: ${issue.title} (${issue.identifier})
|
|
1184
698
|
|
|
1185
|
-
View it at: ${
|
|
699
|
+
View it at: ${issue.url}`;
|
|
1186
700
|
await callback?.({
|
|
1187
701
|
text: successMessage,
|
|
1188
702
|
source: message.content.source
|
|
1189
703
|
});
|
|
1190
704
|
return {
|
|
1191
|
-
text: `
|
|
705
|
+
text: `Created issue: ${issue.title} (${issue.identifier})`,
|
|
1192
706
|
success: true,
|
|
1193
707
|
data: {
|
|
1194
|
-
issueId:
|
|
1195
|
-
identifier:
|
|
1196
|
-
|
|
1197
|
-
url: updatedIssue.url
|
|
708
|
+
issueId: issue.id,
|
|
709
|
+
identifier: issue.identifier,
|
|
710
|
+
url: issue.url
|
|
1198
711
|
}
|
|
1199
712
|
};
|
|
1200
713
|
} catch (error) {
|
|
1201
|
-
|
|
1202
|
-
const errorMessage =
|
|
714
|
+
logger3.error("Failed to create issue:", error);
|
|
715
|
+
const errorMessage = `❌ Failed to create issue: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1203
716
|
await callback?.({
|
|
1204
717
|
text: errorMessage,
|
|
1205
718
|
source: message.content.source
|
|
@@ -1213,71 +726,69 @@ View it at: ${updatedIssue.url}`;
|
|
|
1213
726
|
};
|
|
1214
727
|
|
|
1215
728
|
// src/actions/deleteIssue.ts
|
|
1216
|
-
import {
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
Extract and return ONLY a JSON object (no markdown formatting, no code blocks) with:
|
|
1222
|
-
{
|
|
1223
|
-
"issueId": "The issue identifier (e.g., ENG-123, COM2-7)"
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
Return only the JSON object, no other text.`;
|
|
729
|
+
import {
|
|
730
|
+
logger as logger4,
|
|
731
|
+
ModelType as ModelType3
|
|
732
|
+
} from "@elizaos/core";
|
|
1227
733
|
var deleteIssueAction = {
|
|
1228
734
|
name: "DELETE_LINEAR_ISSUE",
|
|
1229
735
|
description: "Delete (archive) an issue in Linear",
|
|
1230
|
-
similes: [
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
736
|
+
similes: [
|
|
737
|
+
"delete-linear-issue",
|
|
738
|
+
"archive-linear-issue",
|
|
739
|
+
"remove-linear-issue",
|
|
740
|
+
"close-linear-issue"
|
|
741
|
+
],
|
|
742
|
+
examples: [
|
|
743
|
+
[
|
|
744
|
+
{
|
|
745
|
+
name: "User",
|
|
746
|
+
content: {
|
|
747
|
+
text: "Delete issue ENG-123"
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
name: "Assistant",
|
|
752
|
+
content: {
|
|
753
|
+
text: "I'll archive issue ENG-123 for you.",
|
|
754
|
+
actions: ["DELETE_LINEAR_ISSUE"]
|
|
755
|
+
}
|
|
1243
756
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
757
|
+
],
|
|
758
|
+
[
|
|
759
|
+
{
|
|
760
|
+
name: "User",
|
|
761
|
+
content: {
|
|
762
|
+
text: "Remove COM2-7 from Linear"
|
|
763
|
+
}
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
name: "Assistant",
|
|
767
|
+
content: {
|
|
768
|
+
text: "I'll archive issue COM2-7 in Linear.",
|
|
769
|
+
actions: ["DELETE_LINEAR_ISSUE"]
|
|
770
|
+
}
|
|
1257
771
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
772
|
+
],
|
|
773
|
+
[
|
|
774
|
+
{
|
|
775
|
+
name: "User",
|
|
776
|
+
content: {
|
|
777
|
+
text: "Archive the bug report BUG-456"
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
name: "Assistant",
|
|
782
|
+
content: {
|
|
783
|
+
text: "I'll archive issue BUG-456 for you.",
|
|
784
|
+
actions: ["DELETE_LINEAR_ISSUE"]
|
|
785
|
+
}
|
|
1271
786
|
}
|
|
1272
|
-
|
|
1273
|
-
]
|
|
787
|
+
]
|
|
788
|
+
],
|
|
1274
789
|
async validate(runtime, _message, _state) {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
return !!apiKey;
|
|
1278
|
-
} catch {
|
|
1279
|
-
return false;
|
|
1280
|
-
}
|
|
790
|
+
const apiKey = runtime.getSetting("LINEAR_API_KEY");
|
|
791
|
+
return !!apiKey;
|
|
1281
792
|
},
|
|
1282
793
|
async handler(runtime, message, _state, _options, callback) {
|
|
1283
794
|
try {
|
|
@@ -1298,11 +809,12 @@ var deleteIssueAction = {
|
|
|
1298
809
|
};
|
|
1299
810
|
}
|
|
1300
811
|
let issueId;
|
|
1301
|
-
|
|
1302
|
-
|
|
812
|
+
const params = _options?.parameters;
|
|
813
|
+
if (params?.issueId) {
|
|
814
|
+
issueId = params.issueId;
|
|
1303
815
|
} else {
|
|
1304
816
|
const prompt = deleteIssueTemplate.replace("{{userMessage}}", content);
|
|
1305
|
-
const response = await runtime.useModel(
|
|
817
|
+
const response = await runtime.useModel(ModelType3.TEXT_LARGE, {
|
|
1306
818
|
prompt
|
|
1307
819
|
});
|
|
1308
820
|
if (!response) {
|
|
@@ -1316,7 +828,7 @@ var deleteIssueAction = {
|
|
|
1316
828
|
throw new Error("Issue ID not found in parsed response");
|
|
1317
829
|
}
|
|
1318
830
|
} catch (parseError) {
|
|
1319
|
-
|
|
831
|
+
logger4.warn("Failed to parse LLM response, falling back to regex parsing:", parseError);
|
|
1320
832
|
const issueMatch = content.match(/(\w+-\d+)/);
|
|
1321
833
|
if (!issueMatch) {
|
|
1322
834
|
const errorMessage = "Please specify an issue ID (e.g., ENG-123) to delete.";
|
|
@@ -1335,9 +847,9 @@ var deleteIssueAction = {
|
|
|
1335
847
|
const issue = await linearService.getIssue(issueId);
|
|
1336
848
|
const issueTitle = issue.title;
|
|
1337
849
|
const issueIdentifier = issue.identifier;
|
|
1338
|
-
|
|
850
|
+
logger4.info(`Archiving issue ${issueIdentifier}: ${issueTitle}`);
|
|
1339
851
|
await linearService.deleteIssue(issueId);
|
|
1340
|
-
const successMessage =
|
|
852
|
+
const successMessage = `✅ Successfully archived issue ${issueIdentifier}: "${issueTitle}"
|
|
1341
853
|
|
|
1342
854
|
The issue has been moved to the archived state and will no longer appear in active views.`;
|
|
1343
855
|
await callback?.({
|
|
@@ -1355,8 +867,8 @@ The issue has been moved to the archived state and will no longer appear in acti
|
|
|
1355
867
|
}
|
|
1356
868
|
};
|
|
1357
869
|
} catch (error) {
|
|
1358
|
-
|
|
1359
|
-
const errorMessage =
|
|
870
|
+
logger4.error("Failed to delete issue:", error);
|
|
871
|
+
const errorMessage = `❌ Failed to delete issue: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1360
872
|
await callback?.({
|
|
1361
873
|
text: errorMessage,
|
|
1362
874
|
source: message.content.source
|
|
@@ -1369,102 +881,70 @@ The issue has been moved to the archived state and will no longer appear in acti
|
|
|
1369
881
|
}
|
|
1370
882
|
};
|
|
1371
883
|
|
|
1372
|
-
// src/actions/
|
|
884
|
+
// src/actions/getActivity.ts
|
|
1373
885
|
import {
|
|
1374
|
-
|
|
1375
|
-
|
|
886
|
+
logger as logger5,
|
|
887
|
+
ModelType as ModelType4
|
|
1376
888
|
} from "@elizaos/core";
|
|
1377
|
-
var
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
-
|
|
1383
|
-
-
|
|
1384
|
-
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
"dateRange": {
|
|
1401
|
-
"field": "created/updated/completed",
|
|
1402
|
-
"period": "today/yesterday/this-week/last-week/this-month/last-month",
|
|
1403
|
-
"from": "ISO date if specific date",
|
|
1404
|
-
"to": "ISO date if specific date"
|
|
1405
|
-
},
|
|
1406
|
-
"sort": {
|
|
1407
|
-
"field": "created/updated/priority",
|
|
1408
|
-
"order": "asc/desc"
|
|
1409
|
-
},
|
|
1410
|
-
"limit": number (default 10)
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
Only include fields that are clearly mentioned or implied. For "my" issues, set assignees to ["me"].`;
|
|
1414
|
-
var searchIssuesAction = {
|
|
1415
|
-
name: "SEARCH_LINEAR_ISSUES",
|
|
1416
|
-
description: "Search for issues in Linear with various filters",
|
|
1417
|
-
similes: ["search-linear-issues", "find-linear-issues", "query-linear-issues", "list-linear-issues"],
|
|
1418
|
-
examples: [[
|
|
1419
|
-
{
|
|
1420
|
-
name: "User",
|
|
1421
|
-
content: {
|
|
1422
|
-
text: "Show me all open bugs"
|
|
1423
|
-
}
|
|
1424
|
-
},
|
|
1425
|
-
{
|
|
1426
|
-
name: "Assistant",
|
|
1427
|
-
content: {
|
|
1428
|
-
text: "I'll search for all open bug issues in Linear.",
|
|
1429
|
-
actions: ["SEARCH_LINEAR_ISSUES"]
|
|
889
|
+
var getActivityAction = {
|
|
890
|
+
name: "GET_LINEAR_ACTIVITY",
|
|
891
|
+
description: "Get recent Linear activity log with optional filters",
|
|
892
|
+
similes: [
|
|
893
|
+
"get-linear-activity",
|
|
894
|
+
"show-linear-activity",
|
|
895
|
+
"view-linear-activity",
|
|
896
|
+
"check-linear-activity"
|
|
897
|
+
],
|
|
898
|
+
examples: [
|
|
899
|
+
[
|
|
900
|
+
{
|
|
901
|
+
name: "User",
|
|
902
|
+
content: {
|
|
903
|
+
text: "Show me recent Linear activity"
|
|
904
|
+
}
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
name: "Assistant",
|
|
908
|
+
content: {
|
|
909
|
+
text: "I'll show you the recent Linear activity.",
|
|
910
|
+
actions: ["GET_LINEAR_ACTIVITY"]
|
|
911
|
+
}
|
|
1430
912
|
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
913
|
+
],
|
|
914
|
+
[
|
|
915
|
+
{
|
|
916
|
+
name: "User",
|
|
917
|
+
content: {
|
|
918
|
+
text: "What happened in Linear today?"
|
|
919
|
+
}
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
name: "Assistant",
|
|
923
|
+
content: {
|
|
924
|
+
text: "Let me check today's Linear activity for you.",
|
|
925
|
+
actions: ["GET_LINEAR_ACTIVITY"]
|
|
926
|
+
}
|
|
1444
927
|
}
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
928
|
+
],
|
|
929
|
+
[
|
|
930
|
+
{
|
|
931
|
+
name: "User",
|
|
932
|
+
content: {
|
|
933
|
+
text: "Show me what issues John created this week"
|
|
934
|
+
}
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
name: "Assistant",
|
|
938
|
+
content: {
|
|
939
|
+
text: "I'll find the issues John created this week.",
|
|
940
|
+
actions: ["GET_LINEAR_ACTIVITY"]
|
|
941
|
+
}
|
|
1458
942
|
}
|
|
1459
|
-
|
|
1460
|
-
]
|
|
943
|
+
]
|
|
944
|
+
],
|
|
1461
945
|
async validate(runtime, _message, _state) {
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
return !!apiKey;
|
|
1465
|
-
} catch {
|
|
1466
|
-
return false;
|
|
1467
|
-
}
|
|
946
|
+
const apiKey = runtime.getSetting("LINEAR_API_KEY");
|
|
947
|
+
return !!apiKey;
|
|
1468
948
|
},
|
|
1469
949
|
async handler(runtime, message, _state, _options, callback) {
|
|
1470
950
|
try {
|
|
@@ -1472,168 +952,351 @@ var searchIssuesAction = {
|
|
|
1472
952
|
if (!linearService) {
|
|
1473
953
|
throw new Error("Linear service not available");
|
|
1474
954
|
}
|
|
1475
|
-
const content = message.content.text;
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
});
|
|
1482
|
-
return {
|
|
1483
|
-
text: errorMessage,
|
|
1484
|
-
success: false
|
|
1485
|
-
};
|
|
1486
|
-
}
|
|
1487
|
-
let filters = {};
|
|
1488
|
-
if (_options?.filters) {
|
|
1489
|
-
filters = _options.filters;
|
|
1490
|
-
} else {
|
|
1491
|
-
const prompt = searchTemplate.replace("{{userMessage}}", content);
|
|
1492
|
-
const response = await runtime.useModel(ModelType5.TEXT_LARGE, {
|
|
955
|
+
const content = message.content.text || "";
|
|
956
|
+
const filters = {};
|
|
957
|
+
let limit = 10;
|
|
958
|
+
if (content) {
|
|
959
|
+
const prompt = getActivityTemplate.replace("{{userMessage}}", content);
|
|
960
|
+
const response = await runtime.useModel(ModelType4.TEXT_LARGE, {
|
|
1493
961
|
prompt
|
|
1494
962
|
});
|
|
1495
|
-
if (
|
|
1496
|
-
filters = { query: content };
|
|
1497
|
-
} else {
|
|
963
|
+
if (response) {
|
|
1498
964
|
try {
|
|
1499
|
-
const
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
965
|
+
const parsed = JSON.parse(response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim());
|
|
966
|
+
if (parsed.timeRange) {
|
|
967
|
+
const now = new Date;
|
|
968
|
+
let fromDate;
|
|
969
|
+
if (parsed.timeRange.from) {
|
|
970
|
+
fromDate = new Date(parsed.timeRange.from);
|
|
971
|
+
} else if (parsed.timeRange.period) {
|
|
972
|
+
switch (parsed.timeRange.period) {
|
|
973
|
+
case "today":
|
|
974
|
+
fromDate = new Date(now.setHours(0, 0, 0, 0));
|
|
975
|
+
break;
|
|
976
|
+
case "yesterday":
|
|
977
|
+
fromDate = new Date(now.setDate(now.getDate() - 1));
|
|
978
|
+
fromDate.setHours(0, 0, 0, 0);
|
|
979
|
+
break;
|
|
980
|
+
case "this-week":
|
|
981
|
+
fromDate = new Date(now.setDate(now.getDate() - now.getDay()));
|
|
982
|
+
fromDate.setHours(0, 0, 0, 0);
|
|
983
|
+
break;
|
|
984
|
+
case "last-week":
|
|
985
|
+
fromDate = new Date(now.setDate(now.getDate() - now.getDay() - 7));
|
|
986
|
+
fromDate.setHours(0, 0, 0, 0);
|
|
987
|
+
break;
|
|
988
|
+
case "this-month":
|
|
989
|
+
fromDate = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
990
|
+
break;
|
|
1520
991
|
}
|
|
1521
992
|
}
|
|
1522
|
-
if (
|
|
1523
|
-
filters.
|
|
993
|
+
if (fromDate) {
|
|
994
|
+
filters.fromDate = fromDate.toISOString();
|
|
1524
995
|
}
|
|
1525
996
|
}
|
|
1526
|
-
if (parsed.
|
|
1527
|
-
filters.
|
|
1528
|
-
}
|
|
1529
|
-
if (parsed.priorities && parsed.priorities.length > 0) {
|
|
1530
|
-
const priorityMap = {
|
|
1531
|
-
"urgent": 1,
|
|
1532
|
-
"high": 2,
|
|
1533
|
-
"normal": 3,
|
|
1534
|
-
"low": 4,
|
|
1535
|
-
"1": 1,
|
|
1536
|
-
"2": 2,
|
|
1537
|
-
"3": 3,
|
|
1538
|
-
"4": 4
|
|
1539
|
-
};
|
|
1540
|
-
const priorities = parsed.priorities.map((p) => priorityMap[p.toLowerCase()]).filter(Boolean);
|
|
1541
|
-
if (priorities.length > 0) {
|
|
1542
|
-
filters.priority = priorities;
|
|
1543
|
-
}
|
|
997
|
+
if (parsed.actionTypes && parsed.actionTypes.length > 0) {
|
|
998
|
+
filters.action = parsed.actionTypes[0];
|
|
1544
999
|
}
|
|
1545
|
-
if (parsed.
|
|
1546
|
-
filters.
|
|
1000
|
+
if (parsed.resourceTypes && parsed.resourceTypes.length > 0) {
|
|
1001
|
+
filters.resource_type = parsed.resourceTypes[0];
|
|
1547
1002
|
}
|
|
1548
|
-
if (parsed.
|
|
1549
|
-
filters.
|
|
1003
|
+
if (parsed.resourceId) {
|
|
1004
|
+
filters.resource_id = parsed.resourceId;
|
|
1550
1005
|
}
|
|
1551
|
-
if (parsed.
|
|
1552
|
-
|
|
1006
|
+
if (parsed.successFilter && parsed.successFilter !== "all") {
|
|
1007
|
+
filters.success = parsed.successFilter === "success";
|
|
1553
1008
|
}
|
|
1554
|
-
|
|
1555
|
-
if (filters[key] === void 0) {
|
|
1556
|
-
delete filters[key];
|
|
1557
|
-
}
|
|
1558
|
-
});
|
|
1009
|
+
limit = parsed.limit || 10;
|
|
1559
1010
|
} catch (parseError) {
|
|
1560
|
-
|
|
1561
|
-
filters = { query: content };
|
|
1011
|
+
logger5.warn("Failed to parse activity filters:", parseError);
|
|
1562
1012
|
}
|
|
1563
1013
|
}
|
|
1564
1014
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
}
|
|
1015
|
+
let activity = linearService.getActivityLog(limit * 2, filters);
|
|
1016
|
+
if (filters.fromDate) {
|
|
1017
|
+
const fromDateValue = filters.fromDate;
|
|
1018
|
+
const fromDate = typeof fromDateValue === "string" ? fromDateValue : fromDateValue instanceof Date ? fromDateValue.toISOString() : String(fromDateValue);
|
|
1019
|
+
const fromTime = new Date(fromDate).getTime();
|
|
1020
|
+
if (!Number.isNaN(fromTime)) {
|
|
1021
|
+
activity = activity.filter((item) => new Date(item.timestamp).getTime() >= fromTime);
|
|
1573
1022
|
}
|
|
1574
1023
|
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
const noResultsMessage = "No issues found matching your search criteria.";
|
|
1024
|
+
activity = activity.slice(0, limit);
|
|
1025
|
+
if (activity.length === 0) {
|
|
1026
|
+
const noActivityMessage = filters.fromDate ? `No Linear activity found for the specified filters.` : "No recent Linear activity found.";
|
|
1579
1027
|
await callback?.({
|
|
1580
|
-
text:
|
|
1028
|
+
text: noActivityMessage,
|
|
1581
1029
|
source: message.content.source
|
|
1582
1030
|
});
|
|
1583
1031
|
return {
|
|
1584
|
-
text:
|
|
1032
|
+
text: noActivityMessage,
|
|
1585
1033
|
success: true,
|
|
1586
1034
|
data: {
|
|
1587
|
-
|
|
1588
|
-
filters,
|
|
1589
|
-
count: 0
|
|
1035
|
+
activity: []
|
|
1590
1036
|
}
|
|
1591
1037
|
};
|
|
1592
1038
|
}
|
|
1593
|
-
const
|
|
1594
|
-
const
|
|
1595
|
-
const
|
|
1596
|
-
const
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
const resultMessage = `\u{1F4CB} Found ${issues.length} issue${issues.length === 1 ? "" : "s"}:
|
|
1039
|
+
const activityText = activity.map((item, index) => {
|
|
1040
|
+
const time = new Date(item.timestamp).toLocaleString();
|
|
1041
|
+
const status = item.success ? "✅" : "❌";
|
|
1042
|
+
const details = Object.entries(item.details).filter(([key]) => key !== "filters").map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join(", ");
|
|
1043
|
+
return `${index + 1}. ${status} ${item.action} on ${item.resource_type} ${item.resource_id}
|
|
1044
|
+
Time: ${time}
|
|
1045
|
+
${details ? `Details: ${details}` : ""}${item.error ? `
|
|
1046
|
+
Error: ${item.error}` : ""}`;
|
|
1047
|
+
}).join(`
|
|
1603
1048
|
|
|
1604
|
-
|
|
1049
|
+
`);
|
|
1050
|
+
const headerText = filters.fromDate ? `\uD83D\uDCCA Linear activity ${content}:` : "\uD83D\uDCCA Recent Linear activity:";
|
|
1051
|
+
const resultMessage = `${headerText}
|
|
1052
|
+
|
|
1053
|
+
${activityText}`;
|
|
1605
1054
|
await callback?.({
|
|
1606
1055
|
text: resultMessage,
|
|
1607
1056
|
source: message.content.source
|
|
1608
1057
|
});
|
|
1609
1058
|
return {
|
|
1610
|
-
text: `Found ${
|
|
1059
|
+
text: `Found ${activity.length} activity item${activity.length === 1 ? "" : "s"}`,
|
|
1611
1060
|
success: true,
|
|
1612
1061
|
data: {
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1062
|
+
activity: activity.map((item) => ({
|
|
1063
|
+
id: item.id,
|
|
1064
|
+
action: item.action,
|
|
1065
|
+
resource_type: item.resource_type,
|
|
1066
|
+
resource_id: item.resource_id,
|
|
1067
|
+
success: item.success,
|
|
1068
|
+
error: item.error,
|
|
1069
|
+
details: JSON.stringify(item.details),
|
|
1070
|
+
timestamp: typeof item.timestamp === "string" ? item.timestamp : new Date(item.timestamp).toISOString()
|
|
1071
|
+
})),
|
|
1072
|
+
filters: filters ? {
|
|
1073
|
+
...filters,
|
|
1074
|
+
fromDate: filters.fromDate ? typeof filters.fromDate === "string" ? filters.fromDate : String(filters.fromDate) : undefined
|
|
1075
|
+
} : undefined,
|
|
1076
|
+
count: activity.length
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
} catch (error) {
|
|
1080
|
+
logger5.error("Failed to get activity:", error);
|
|
1081
|
+
const errorMessage = `❌ Failed to get activity: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1082
|
+
await callback?.({
|
|
1083
|
+
text: errorMessage,
|
|
1084
|
+
source: message.content.source
|
|
1085
|
+
});
|
|
1086
|
+
return {
|
|
1087
|
+
text: errorMessage,
|
|
1088
|
+
success: false
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
// src/actions/getIssue.ts
|
|
1095
|
+
import {
|
|
1096
|
+
logger as logger6,
|
|
1097
|
+
ModelType as ModelType5
|
|
1098
|
+
} from "@elizaos/core";
|
|
1099
|
+
var getIssueAction = {
|
|
1100
|
+
name: "GET_LINEAR_ISSUE",
|
|
1101
|
+
description: "Get details of a specific Linear issue",
|
|
1102
|
+
similes: [
|
|
1103
|
+
"get-linear-issue",
|
|
1104
|
+
"show-linear-issue",
|
|
1105
|
+
"view-linear-issue",
|
|
1106
|
+
"check-linear-issue",
|
|
1107
|
+
"find-linear-issue"
|
|
1108
|
+
],
|
|
1109
|
+
examples: [
|
|
1110
|
+
[
|
|
1111
|
+
{
|
|
1112
|
+
name: "User",
|
|
1113
|
+
content: {
|
|
1114
|
+
text: "Show me issue ENG-123"
|
|
1115
|
+
}
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
name: "Assistant",
|
|
1119
|
+
content: {
|
|
1120
|
+
text: "I'll get the details for issue ENG-123.",
|
|
1121
|
+
actions: ["GET_LINEAR_ISSUE"]
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
],
|
|
1125
|
+
[
|
|
1126
|
+
{
|
|
1127
|
+
name: "User",
|
|
1128
|
+
content: {
|
|
1129
|
+
text: "What's the status of the login bug?"
|
|
1130
|
+
}
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
name: "Assistant",
|
|
1134
|
+
content: {
|
|
1135
|
+
text: "Let me find the login bug issue for you.",
|
|
1136
|
+
actions: ["GET_LINEAR_ISSUE"]
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
],
|
|
1140
|
+
[
|
|
1141
|
+
{
|
|
1142
|
+
name: "User",
|
|
1143
|
+
content: {
|
|
1144
|
+
text: "Show me the latest high priority issue assigned to Sarah"
|
|
1145
|
+
}
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
name: "Assistant",
|
|
1149
|
+
content: {
|
|
1150
|
+
text: "I'll find the latest high priority issue assigned to Sarah.",
|
|
1151
|
+
actions: ["GET_LINEAR_ISSUE"]
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
]
|
|
1155
|
+
],
|
|
1156
|
+
async validate(runtime, _message, _state) {
|
|
1157
|
+
const apiKey = runtime.getSetting("LINEAR_API_KEY");
|
|
1158
|
+
return !!apiKey;
|
|
1159
|
+
},
|
|
1160
|
+
async handler(runtime, message, _state, _options, callback) {
|
|
1161
|
+
try {
|
|
1162
|
+
const linearService = runtime.getService("linear");
|
|
1163
|
+
if (!linearService) {
|
|
1164
|
+
throw new Error("Linear service not available");
|
|
1165
|
+
}
|
|
1166
|
+
const content = message.content.text;
|
|
1167
|
+
if (!content) {
|
|
1168
|
+
const errorMessage2 = "Please specify which issue you want to see.";
|
|
1169
|
+
await callback?.({
|
|
1170
|
+
text: errorMessage2,
|
|
1171
|
+
source: message.content.source
|
|
1172
|
+
});
|
|
1173
|
+
return {
|
|
1174
|
+
text: errorMessage2,
|
|
1175
|
+
success: false
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
const prompt = getIssueTemplate.replace("{{userMessage}}", content);
|
|
1179
|
+
const response = await runtime.useModel(ModelType5.TEXT_LARGE, {
|
|
1180
|
+
prompt
|
|
1181
|
+
});
|
|
1182
|
+
if (!response) {
|
|
1183
|
+
const issueMatch = content.match(/(\w+-\d+)/);
|
|
1184
|
+
if (issueMatch) {
|
|
1185
|
+
const issue = await linearService.getIssue(issueMatch[1]);
|
|
1186
|
+
return await formatIssueResponse(issue, callback, message);
|
|
1187
|
+
}
|
|
1188
|
+
throw new Error("Could not understand issue reference");
|
|
1189
|
+
}
|
|
1190
|
+
try {
|
|
1191
|
+
const parsed = JSON.parse(response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim());
|
|
1192
|
+
if (parsed.directId) {
|
|
1193
|
+
const issue = await linearService.getIssue(parsed.directId);
|
|
1194
|
+
return await formatIssueResponse(issue, callback, message);
|
|
1195
|
+
}
|
|
1196
|
+
if (parsed.searchBy && Object.keys(parsed.searchBy).length > 0) {
|
|
1197
|
+
const filters = {};
|
|
1198
|
+
if (parsed.searchBy.title) {
|
|
1199
|
+
filters.query = parsed.searchBy.title;
|
|
1200
|
+
}
|
|
1201
|
+
if (parsed.searchBy.assignee) {
|
|
1202
|
+
filters.assignee = [parsed.searchBy.assignee];
|
|
1203
|
+
}
|
|
1204
|
+
if (parsed.searchBy.priority) {
|
|
1205
|
+
const priorityMap = {
|
|
1206
|
+
urgent: 1,
|
|
1207
|
+
high: 2,
|
|
1208
|
+
normal: 3,
|
|
1209
|
+
low: 4,
|
|
1210
|
+
"1": 1,
|
|
1211
|
+
"2": 2,
|
|
1212
|
+
"3": 3,
|
|
1213
|
+
"4": 4
|
|
1214
|
+
};
|
|
1215
|
+
const priority = priorityMap[parsed.searchBy.priority.toLowerCase()];
|
|
1216
|
+
if (priority) {
|
|
1217
|
+
filters.priority = [priority];
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
if (parsed.searchBy.team) {
|
|
1221
|
+
filters.team = parsed.searchBy.team;
|
|
1222
|
+
}
|
|
1223
|
+
if (parsed.searchBy.state) {
|
|
1224
|
+
filters.state = [parsed.searchBy.state];
|
|
1225
|
+
}
|
|
1226
|
+
const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
|
|
1227
|
+
if (defaultTeamKey && !filters.team) {
|
|
1228
|
+
filters.team = defaultTeamKey;
|
|
1229
|
+
}
|
|
1230
|
+
const issues = await linearService.searchIssues({
|
|
1231
|
+
...filters,
|
|
1232
|
+
limit: parsed.searchBy.recency ? 10 : 5
|
|
1233
|
+
});
|
|
1234
|
+
if (issues.length === 0) {
|
|
1235
|
+
const noResultsMessage = "No issues found matching your criteria.";
|
|
1236
|
+
await callback?.({
|
|
1237
|
+
text: noResultsMessage,
|
|
1238
|
+
source: message.content.source
|
|
1239
|
+
});
|
|
1617
1240
|
return {
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
title: issue.title,
|
|
1621
|
-
url: issue.url,
|
|
1622
|
-
priority: issue.priority,
|
|
1623
|
-
state: state ? { name: state.name, type: state.type } : null,
|
|
1624
|
-
assignee: assignee ? { name: assignee.name, email: assignee.email } : null,
|
|
1625
|
-
team: team ? { name: team.name, key: team.key } : null,
|
|
1626
|
-
createdAt: issue.createdAt,
|
|
1627
|
-
updatedAt: issue.updatedAt
|
|
1241
|
+
text: noResultsMessage,
|
|
1242
|
+
success: false
|
|
1628
1243
|
};
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
|
|
1244
|
+
}
|
|
1245
|
+
if (parsed.searchBy.recency) {
|
|
1246
|
+
issues.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
1247
|
+
}
|
|
1248
|
+
if (parsed.searchBy.recency && issues.length > 0) {
|
|
1249
|
+
return await formatIssueResponse(issues[0], callback, message);
|
|
1250
|
+
}
|
|
1251
|
+
if (issues.length === 1) {
|
|
1252
|
+
return await formatIssueResponse(issues[0], callback, message);
|
|
1253
|
+
}
|
|
1254
|
+
const issueList = await Promise.all(issues.slice(0, 5).map(async (issue, index) => {
|
|
1255
|
+
const state = await issue.state;
|
|
1256
|
+
return `${index + 1}. ${issue.identifier}: ${issue.title} (${state?.name || "No state"})`;
|
|
1257
|
+
}));
|
|
1258
|
+
const clarifyMessage = `Found ${issues.length} issues matching your criteria:
|
|
1259
|
+
${issueList.join(`
|
|
1260
|
+
`)}
|
|
1261
|
+
|
|
1262
|
+
Please specify which one you want to see by its ID.`;
|
|
1263
|
+
await callback?.({
|
|
1264
|
+
text: clarifyMessage,
|
|
1265
|
+
source: message.content.source
|
|
1266
|
+
});
|
|
1267
|
+
return {
|
|
1268
|
+
text: clarifyMessage,
|
|
1269
|
+
success: true,
|
|
1270
|
+
data: {
|
|
1271
|
+
multipleResults: true,
|
|
1272
|
+
issues: issues.slice(0, 5).map((i) => ({
|
|
1273
|
+
id: i.id,
|
|
1274
|
+
identifier: i.identifier,
|
|
1275
|
+
title: i.title
|
|
1276
|
+
}))
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
} catch (parseError) {
|
|
1281
|
+
logger6.warn("Failed to parse LLM response, falling back to regex:", parseError);
|
|
1282
|
+
const issueMatch = content.match(/(\w+-\d+)/);
|
|
1283
|
+
if (issueMatch) {
|
|
1284
|
+
const issue = await linearService.getIssue(issueMatch[1]);
|
|
1285
|
+
return await formatIssueResponse(issue, callback, message);
|
|
1632
1286
|
}
|
|
1287
|
+
}
|
|
1288
|
+
const errorMessage = "Could not understand which issue you want to see. Please provide an issue ID (e.g., ENG-123) or describe it more specifically.";
|
|
1289
|
+
await callback?.({
|
|
1290
|
+
text: errorMessage,
|
|
1291
|
+
source: message.content.source
|
|
1292
|
+
});
|
|
1293
|
+
return {
|
|
1294
|
+
text: errorMessage,
|
|
1295
|
+
success: false
|
|
1633
1296
|
};
|
|
1634
1297
|
} catch (error) {
|
|
1635
|
-
logger6.error("Failed to
|
|
1636
|
-
const errorMessage =
|
|
1298
|
+
logger6.error("Failed to get issue:", error);
|
|
1299
|
+
const errorMessage = `❌ Failed to get issue: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1637
1300
|
await callback?.({
|
|
1638
1301
|
text: errorMessage,
|
|
1639
1302
|
source: message.content.source
|
|
@@ -1645,83 +1308,148 @@ ${issueText}`;
|
|
|
1645
1308
|
}
|
|
1646
1309
|
}
|
|
1647
1310
|
};
|
|
1311
|
+
async function formatIssueResponse(issue, callback, message) {
|
|
1312
|
+
const assignee = await issue.assignee;
|
|
1313
|
+
const state = await issue.state;
|
|
1314
|
+
const team = await issue.team;
|
|
1315
|
+
const labels = await issue.labels();
|
|
1316
|
+
const project = await issue.project;
|
|
1317
|
+
const issueDetails = {
|
|
1318
|
+
id: issue.id,
|
|
1319
|
+
identifier: issue.identifier,
|
|
1320
|
+
title: issue.title,
|
|
1321
|
+
description: issue.description,
|
|
1322
|
+
priority: issue.priority,
|
|
1323
|
+
priorityLabel: issue.priorityLabel,
|
|
1324
|
+
url: issue.url,
|
|
1325
|
+
createdAt: issue.createdAt,
|
|
1326
|
+
updatedAt: issue.updatedAt,
|
|
1327
|
+
dueDate: issue.dueDate,
|
|
1328
|
+
estimate: issue.estimate,
|
|
1329
|
+
assignee: assignee ? {
|
|
1330
|
+
id: assignee.id,
|
|
1331
|
+
name: assignee.name,
|
|
1332
|
+
email: assignee.email
|
|
1333
|
+
} : null,
|
|
1334
|
+
state: state ? {
|
|
1335
|
+
id: state.id,
|
|
1336
|
+
name: state.name,
|
|
1337
|
+
type: state.type,
|
|
1338
|
+
color: state.color
|
|
1339
|
+
} : null,
|
|
1340
|
+
team: team ? {
|
|
1341
|
+
id: team.id,
|
|
1342
|
+
name: team.name,
|
|
1343
|
+
key: team.key
|
|
1344
|
+
} : null,
|
|
1345
|
+
labels: labels.nodes.map((label) => ({
|
|
1346
|
+
id: label.id,
|
|
1347
|
+
name: label.name,
|
|
1348
|
+
color: label.color
|
|
1349
|
+
})),
|
|
1350
|
+
project: project ? {
|
|
1351
|
+
id: project.id,
|
|
1352
|
+
name: project.name,
|
|
1353
|
+
description: project.description
|
|
1354
|
+
} : null
|
|
1355
|
+
};
|
|
1356
|
+
const priorityLabels = ["", "Urgent", "High", "Normal", "Low"];
|
|
1357
|
+
const priority = priorityLabels[issue.priority || 0] || "No priority";
|
|
1358
|
+
const labelText = issueDetails.labels.length > 0 ? `Labels: ${issueDetails.labels.map((l) => l.name).join(", ")}` : "";
|
|
1359
|
+
const issueMessage = `\uD83D\uDCCB **${issue.identifier}: ${issue.title}**
|
|
1360
|
+
|
|
1361
|
+
Status: ${state?.name || "No status"}
|
|
1362
|
+
Priority: ${priority}
|
|
1363
|
+
Team: ${team?.name || "No team"}
|
|
1364
|
+
Assignee: ${assignee?.name || "Unassigned"}
|
|
1365
|
+
${issue.dueDate ? `Due: ${new Date(issue.dueDate).toLocaleDateString()}` : ""}
|
|
1366
|
+
${labelText}
|
|
1367
|
+
${project ? `Project: ${project.name}` : ""}
|
|
1648
1368
|
|
|
1649
|
-
|
|
1650
|
-
import { logger as logger7, ModelType as ModelType6 } from "@elizaos/core";
|
|
1651
|
-
var createCommentTemplate = `Extract comment details from the user's request to add a comment to a Linear issue.
|
|
1652
|
-
|
|
1653
|
-
User request: "{{userMessage}}"
|
|
1654
|
-
|
|
1655
|
-
The user might express this in various ways:
|
|
1656
|
-
- "Comment on ENG-123: This looks good"
|
|
1657
|
-
- "Tell ENG-123 that the fix is ready for testing"
|
|
1658
|
-
- "Add a note to the login bug saying we need more info"
|
|
1659
|
-
- "Reply to COM2-7: Thanks for the update"
|
|
1660
|
-
- "Let the payment issue know that it's blocked by API changes"
|
|
1369
|
+
${issue.description || "No description"}
|
|
1661
1370
|
|
|
1662
|
-
|
|
1663
|
-
{
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1371
|
+
View in Linear: ${issue.url}`;
|
|
1372
|
+
await callback?.({
|
|
1373
|
+
text: issueMessage,
|
|
1374
|
+
source: message.content.source
|
|
1375
|
+
});
|
|
1376
|
+
const serializedIssue = {
|
|
1377
|
+
...issueDetails,
|
|
1378
|
+
createdAt: issueDetails.createdAt instanceof Date ? issueDetails.createdAt.toISOString() : issueDetails.createdAt,
|
|
1379
|
+
updatedAt: issueDetails.updatedAt instanceof Date ? issueDetails.updatedAt.toISOString() : issueDetails.updatedAt,
|
|
1380
|
+
dueDate: issueDetails.dueDate ? issueDetails.dueDate instanceof Date ? issueDetails.dueDate.toISOString() : issueDetails.dueDate : null
|
|
1381
|
+
};
|
|
1382
|
+
return {
|
|
1383
|
+
text: `Retrieved issue ${issue.identifier}: ${issue.title}`,
|
|
1384
|
+
success: true,
|
|
1385
|
+
data: { issue: serializedIssue }
|
|
1386
|
+
};
|
|
1668
1387
|
}
|
|
1669
1388
|
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1389
|
+
// src/actions/listProjects.ts
|
|
1390
|
+
import {
|
|
1391
|
+
logger as logger7,
|
|
1392
|
+
ModelType as ModelType6
|
|
1393
|
+
} from "@elizaos/core";
|
|
1394
|
+
var listProjectsAction = {
|
|
1395
|
+
name: "LIST_LINEAR_PROJECTS",
|
|
1396
|
+
description: "List projects in Linear with optional filters",
|
|
1397
|
+
similes: [
|
|
1398
|
+
"list-linear-projects",
|
|
1399
|
+
"show-linear-projects",
|
|
1400
|
+
"get-linear-projects",
|
|
1401
|
+
"view-linear-projects"
|
|
1402
|
+
],
|
|
1403
|
+
examples: [
|
|
1404
|
+
[
|
|
1405
|
+
{
|
|
1406
|
+
name: "User",
|
|
1407
|
+
content: {
|
|
1408
|
+
text: "Show me all projects"
|
|
1409
|
+
}
|
|
1410
|
+
},
|
|
1411
|
+
{
|
|
1412
|
+
name: "Assistant",
|
|
1413
|
+
content: {
|
|
1414
|
+
text: "I'll list all the projects in Linear for you.",
|
|
1415
|
+
actions: ["LIST_LINEAR_PROJECTS"]
|
|
1416
|
+
}
|
|
1687
1417
|
}
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1418
|
+
],
|
|
1419
|
+
[
|
|
1420
|
+
{
|
|
1421
|
+
name: "User",
|
|
1422
|
+
content: {
|
|
1423
|
+
text: "What active projects do we have?"
|
|
1424
|
+
}
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
name: "Assistant",
|
|
1428
|
+
content: {
|
|
1429
|
+
text: "Let me show you all the active projects.",
|
|
1430
|
+
actions: ["LIST_LINEAR_PROJECTS"]
|
|
1431
|
+
}
|
|
1701
1432
|
}
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1433
|
+
],
|
|
1434
|
+
[
|
|
1435
|
+
{
|
|
1436
|
+
name: "User",
|
|
1437
|
+
content: {
|
|
1438
|
+
text: "Show me projects for the engineering team"
|
|
1439
|
+
}
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
name: "Assistant",
|
|
1443
|
+
content: {
|
|
1444
|
+
text: "I'll find the projects for the engineering team.",
|
|
1445
|
+
actions: ["LIST_LINEAR_PROJECTS"]
|
|
1446
|
+
}
|
|
1715
1447
|
}
|
|
1716
|
-
|
|
1717
|
-
]
|
|
1448
|
+
]
|
|
1449
|
+
],
|
|
1718
1450
|
async validate(runtime, _message, _state) {
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
return !!apiKey;
|
|
1722
|
-
} catch {
|
|
1723
|
-
return false;
|
|
1724
|
-
}
|
|
1451
|
+
const apiKey = runtime.getSetting("LINEAR_API_KEY");
|
|
1452
|
+
return !!apiKey;
|
|
1725
1453
|
},
|
|
1726
1454
|
async handler(runtime, message, _state, _options, callback) {
|
|
1727
1455
|
try {
|
|
@@ -1729,153 +1457,151 @@ var createCommentAction = {
|
|
|
1729
1457
|
if (!linearService) {
|
|
1730
1458
|
throw new Error("Linear service not available");
|
|
1731
1459
|
}
|
|
1732
|
-
const content = message.content.text;
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
});
|
|
1739
|
-
return {
|
|
1740
|
-
text: errorMessage,
|
|
1741
|
-
success: false
|
|
1742
|
-
};
|
|
1743
|
-
}
|
|
1744
|
-
let issueId;
|
|
1745
|
-
let commentBody;
|
|
1746
|
-
if (_options?.issueId && _options?.body) {
|
|
1747
|
-
issueId = _options.issueId;
|
|
1748
|
-
commentBody = _options.body;
|
|
1749
|
-
} else {
|
|
1750
|
-
const prompt = createCommentTemplate.replace("{{userMessage}}", content);
|
|
1460
|
+
const content = message.content.text || "";
|
|
1461
|
+
let teamId;
|
|
1462
|
+
let showAll = false;
|
|
1463
|
+
let stateFilter;
|
|
1464
|
+
if (content) {
|
|
1465
|
+
const prompt = listProjectsTemplate.replace("{{userMessage}}", content);
|
|
1751
1466
|
const response = await runtime.useModel(ModelType6.TEXT_LARGE, {
|
|
1752
1467
|
prompt
|
|
1753
1468
|
});
|
|
1754
|
-
if (
|
|
1755
|
-
const issueMatch = content.match(/(?:comment on|add.*comment.*to|reply to|tell)\s+(\w+-\d+):?\s*(.*)/i);
|
|
1756
|
-
if (issueMatch) {
|
|
1757
|
-
issueId = issueMatch[1];
|
|
1758
|
-
commentBody = issueMatch[2].trim();
|
|
1759
|
-
} else {
|
|
1760
|
-
throw new Error("Could not understand comment request");
|
|
1761
|
-
}
|
|
1762
|
-
} else {
|
|
1469
|
+
if (response) {
|
|
1763
1470
|
try {
|
|
1764
1471
|
const parsed = JSON.parse(response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim());
|
|
1765
|
-
if (parsed.
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
limit: 5
|
|
1772
|
-
};
|
|
1773
|
-
const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
|
|
1774
|
-
if (defaultTeamKey) {
|
|
1775
|
-
filters.team = defaultTeamKey;
|
|
1776
|
-
}
|
|
1777
|
-
const issues = await linearService.searchIssues(filters);
|
|
1778
|
-
if (issues.length === 0) {
|
|
1779
|
-
const errorMessage = `No issues found matching "${parsed.issueDescription}". Please provide a specific issue ID.`;
|
|
1780
|
-
await callback?.({
|
|
1781
|
-
text: errorMessage,
|
|
1782
|
-
source: message.content.source
|
|
1783
|
-
});
|
|
1784
|
-
return {
|
|
1785
|
-
text: errorMessage,
|
|
1786
|
-
success: false
|
|
1787
|
-
};
|
|
1788
|
-
}
|
|
1789
|
-
if (issues.length === 1) {
|
|
1790
|
-
issueId = issues[0].identifier;
|
|
1791
|
-
commentBody = parsed.commentBody;
|
|
1792
|
-
} else {
|
|
1793
|
-
const issueList = await Promise.all(issues.map(async (issue2, index) => {
|
|
1794
|
-
const state = await issue2.state;
|
|
1795
|
-
return `${index + 1}. ${issue2.identifier}: ${issue2.title} (${state?.name || "No state"})`;
|
|
1796
|
-
}));
|
|
1797
|
-
const clarifyMessage = `Found multiple issues matching "${parsed.issueDescription}":
|
|
1798
|
-
${issueList.join("\n")}
|
|
1799
|
-
|
|
1800
|
-
Please specify which issue to comment on by its ID.`;
|
|
1801
|
-
await callback?.({
|
|
1802
|
-
text: clarifyMessage,
|
|
1803
|
-
source: message.content.source
|
|
1804
|
-
});
|
|
1805
|
-
return {
|
|
1806
|
-
text: clarifyMessage,
|
|
1807
|
-
success: false,
|
|
1808
|
-
data: {
|
|
1809
|
-
multipleMatches: true,
|
|
1810
|
-
issues: issues.map((i) => ({
|
|
1811
|
-
id: i.id,
|
|
1812
|
-
identifier: i.identifier,
|
|
1813
|
-
title: i.title
|
|
1814
|
-
})),
|
|
1815
|
-
pendingComment: parsed.commentBody
|
|
1816
|
-
}
|
|
1817
|
-
};
|
|
1472
|
+
if (parsed.teamFilter) {
|
|
1473
|
+
const teams = await linearService.getTeams();
|
|
1474
|
+
const team = teams.find((t) => t.key.toLowerCase() === parsed.teamFilter.toLowerCase() || t.name.toLowerCase() === parsed.teamFilter.toLowerCase());
|
|
1475
|
+
if (team) {
|
|
1476
|
+
teamId = team.id;
|
|
1477
|
+
logger7.info(`Filtering projects by team: ${team.name} (${team.key})`);
|
|
1818
1478
|
}
|
|
1819
|
-
} else {
|
|
1820
|
-
throw new Error("No issue identifier or description found");
|
|
1821
|
-
}
|
|
1822
|
-
if (parsed.commentType && parsed.commentType !== "note") {
|
|
1823
|
-
commentBody = `[${parsed.commentType.toUpperCase()}] ${commentBody}`;
|
|
1824
1479
|
}
|
|
1480
|
+
showAll = parsed.showAll === true;
|
|
1481
|
+
stateFilter = parsed.stateFilter;
|
|
1825
1482
|
} catch (parseError) {
|
|
1826
|
-
logger7.warn("Failed to parse
|
|
1827
|
-
const
|
|
1828
|
-
if (
|
|
1829
|
-
const
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
text: errorMessage,
|
|
1836
|
-
success: false
|
|
1837
|
-
};
|
|
1483
|
+
logger7.warn("Failed to parse project filters, using basic parsing:", parseError);
|
|
1484
|
+
const teamMatch = content.match(/(?:for|in|of)\s+(?:the\s+)?(\w+)\s+team/i);
|
|
1485
|
+
if (teamMatch) {
|
|
1486
|
+
const teams = await linearService.getTeams();
|
|
1487
|
+
const team = teams.find((t) => t.key.toLowerCase() === teamMatch[1].toLowerCase() || t.name.toLowerCase() === teamMatch[1].toLowerCase());
|
|
1488
|
+
if (team) {
|
|
1489
|
+
teamId = team.id;
|
|
1490
|
+
logger7.info(`Filtering projects by team: ${team.name} (${team.key})`);
|
|
1491
|
+
}
|
|
1838
1492
|
}
|
|
1839
|
-
|
|
1840
|
-
commentBody = issueMatch[2].trim();
|
|
1493
|
+
showAll = content.toLowerCase().includes("all") && content.toLowerCase().includes("project");
|
|
1841
1494
|
}
|
|
1842
1495
|
}
|
|
1843
1496
|
}
|
|
1844
|
-
if (!
|
|
1845
|
-
const
|
|
1497
|
+
if (!teamId && !showAll) {
|
|
1498
|
+
const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
|
|
1499
|
+
if (defaultTeamKey) {
|
|
1500
|
+
const teams = await linearService.getTeams();
|
|
1501
|
+
const defaultTeam = teams.find((t) => t.key.toLowerCase() === defaultTeamKey.toLowerCase());
|
|
1502
|
+
if (defaultTeam) {
|
|
1503
|
+
teamId = defaultTeam.id;
|
|
1504
|
+
logger7.info(`Applying default team filter for projects: ${defaultTeam.name} (${defaultTeam.key})`);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
let projects = await linearService.getProjects(teamId);
|
|
1509
|
+
if (stateFilter && stateFilter !== "all") {
|
|
1510
|
+
projects = projects.filter((project) => {
|
|
1511
|
+
const state = project.state?.toLowerCase() || "";
|
|
1512
|
+
if (stateFilter === "active") {
|
|
1513
|
+
return state === "started" || state === "in progress" || !state;
|
|
1514
|
+
} else if (stateFilter === "planned") {
|
|
1515
|
+
return state === "planned" || state === "backlog";
|
|
1516
|
+
} else if (stateFilter === "completed") {
|
|
1517
|
+
return state === "completed" || state === "done" || state === "canceled";
|
|
1518
|
+
}
|
|
1519
|
+
return true;
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
if (projects.length === 0) {
|
|
1523
|
+
const noProjectsMessage = teamId ? "No projects found for the specified team." : "No projects found in Linear.";
|
|
1846
1524
|
await callback?.({
|
|
1847
|
-
text:
|
|
1525
|
+
text: noProjectsMessage,
|
|
1848
1526
|
source: message.content.source
|
|
1849
1527
|
});
|
|
1850
1528
|
return {
|
|
1851
|
-
text:
|
|
1852
|
-
success:
|
|
1529
|
+
text: noProjectsMessage,
|
|
1530
|
+
success: true,
|
|
1531
|
+
data: {
|
|
1532
|
+
projects: []
|
|
1533
|
+
}
|
|
1853
1534
|
};
|
|
1854
1535
|
}
|
|
1855
|
-
const
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1536
|
+
const projectsWithDetails = await Promise.all(projects.map(async (project) => {
|
|
1537
|
+
const teamsQuery = await project.teams();
|
|
1538
|
+
const teams = await teamsQuery.nodes;
|
|
1539
|
+
const lead = await project.lead;
|
|
1540
|
+
return {
|
|
1541
|
+
...project,
|
|
1542
|
+
teamsList: teams,
|
|
1543
|
+
leadUser: lead
|
|
1544
|
+
};
|
|
1545
|
+
}));
|
|
1546
|
+
const projectList = projectsWithDetails.map((project, index) => {
|
|
1547
|
+
const teamNames = project.teamsList.map((t) => t.name).join(", ") || "No teams";
|
|
1548
|
+
const status = project.state || "Active";
|
|
1549
|
+
const progress = project.progress ? ` (${Math.round(project.progress * 100)}% complete)` : "";
|
|
1550
|
+
const lead = project.leadUser ? ` | Lead: ${project.leadUser.name}` : "";
|
|
1551
|
+
const dates = [];
|
|
1552
|
+
if (project.startDate)
|
|
1553
|
+
dates.push(`Start: ${new Date(project.startDate).toLocaleDateString()}`);
|
|
1554
|
+
if (project.targetDate)
|
|
1555
|
+
dates.push(`Due: ${new Date(project.targetDate).toLocaleDateString()}`);
|
|
1556
|
+
const dateInfo = dates.length > 0 ? `
|
|
1557
|
+
${dates.join(" | ")}` : "";
|
|
1558
|
+
return `${index + 1}. ${project.name}${project.description ? ` - ${project.description}` : ""}
|
|
1559
|
+
Status: ${status}${progress} | Teams: ${teamNames}${lead}${dateInfo}`;
|
|
1560
|
+
}).join(`
|
|
1561
|
+
|
|
1562
|
+
`);
|
|
1563
|
+
const headerText = stateFilter && stateFilter !== "all" ? `\uD83D\uDCC1 Found ${projects.length} ${stateFilter} project${projects.length === 1 ? "" : "s"}:` : `\uD83D\uDCC1 Found ${projects.length} project${projects.length === 1 ? "" : "s"}:`;
|
|
1564
|
+
const resultMessage = `${headerText}
|
|
1565
|
+
|
|
1566
|
+
${projectList}`;
|
|
1861
1567
|
await callback?.({
|
|
1862
|
-
text:
|
|
1568
|
+
text: resultMessage,
|
|
1863
1569
|
source: message.content.source
|
|
1864
1570
|
});
|
|
1865
1571
|
return {
|
|
1866
|
-
text: `
|
|
1572
|
+
text: `Found ${projects.length} project${projects.length === 1 ? "" : "s"}`,
|
|
1867
1573
|
success: true,
|
|
1868
1574
|
data: {
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1575
|
+
projects: projectsWithDetails.map((p) => ({
|
|
1576
|
+
id: p.id,
|
|
1577
|
+
name: p.name,
|
|
1578
|
+
description: p.description,
|
|
1579
|
+
url: p.url,
|
|
1580
|
+
teams: p.teamsList.map((t) => ({
|
|
1581
|
+
id: t.id,
|
|
1582
|
+
name: t.name,
|
|
1583
|
+
key: t.key
|
|
1584
|
+
})),
|
|
1585
|
+
lead: p.leadUser ? {
|
|
1586
|
+
id: p.leadUser.id,
|
|
1587
|
+
name: p.leadUser.name,
|
|
1588
|
+
email: p.leadUser.email
|
|
1589
|
+
} : null,
|
|
1590
|
+
state: p.state,
|
|
1591
|
+
progress: p.progress,
|
|
1592
|
+
startDate: p.startDate,
|
|
1593
|
+
targetDate: p.targetDate
|
|
1594
|
+
})),
|
|
1595
|
+
count: projects.length,
|
|
1596
|
+
filters: {
|
|
1597
|
+
team: teamId,
|
|
1598
|
+
state: stateFilter
|
|
1599
|
+
}
|
|
1874
1600
|
}
|
|
1875
1601
|
};
|
|
1876
1602
|
} catch (error) {
|
|
1877
|
-
logger7.error("Failed to
|
|
1878
|
-
const errorMessage =
|
|
1603
|
+
logger7.error("Failed to list projects:", error);
|
|
1604
|
+
const errorMessage = `❌ Failed to list projects: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1879
1605
|
await callback?.({
|
|
1880
1606
|
text: errorMessage,
|
|
1881
1607
|
source: message.content.source
|
|
@@ -1889,84 +1615,64 @@ Please specify which issue to comment on by its ID.`;
|
|
|
1889
1615
|
};
|
|
1890
1616
|
|
|
1891
1617
|
// src/actions/listTeams.ts
|
|
1892
|
-
import {
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
The user might ask for teams in various ways:
|
|
1898
|
-
- "Show me all teams" \u2192 list all teams
|
|
1899
|
-
- "Engineering teams" \u2192 filter by teams with engineering in name/description
|
|
1900
|
-
- "List teams I'm part of" \u2192 filter by membership
|
|
1901
|
-
- "Which teams work on the mobile app?" \u2192 filter by description/focus
|
|
1902
|
-
- "Show me the ELIZA team details" \u2192 specific team lookup
|
|
1903
|
-
- "Active teams" \u2192 teams with recent activity
|
|
1904
|
-
- "Frontend and backend teams" \u2192 multiple team types
|
|
1905
|
-
|
|
1906
|
-
Return ONLY a JSON object:
|
|
1907
|
-
{
|
|
1908
|
-
"nameFilter": "Keywords to search in team names",
|
|
1909
|
-
"specificTeam": "Specific team name or key if looking for one team",
|
|
1910
|
-
"myTeams": true/false (true if user wants their teams),
|
|
1911
|
-
"showAll": true/false (true if user explicitly asks for "all"),
|
|
1912
|
-
"includeDetails": true/false (true if user wants detailed info)
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
Only include fields that are clearly mentioned.`;
|
|
1618
|
+
import {
|
|
1619
|
+
logger as logger8,
|
|
1620
|
+
ModelType as ModelType7
|
|
1621
|
+
} from "@elizaos/core";
|
|
1916
1622
|
var listTeamsAction = {
|
|
1917
1623
|
name: "LIST_LINEAR_TEAMS",
|
|
1918
1624
|
description: "List teams in Linear with optional filters",
|
|
1919
1625
|
similes: ["list-linear-teams", "show-linear-teams", "get-linear-teams", "view-linear-teams"],
|
|
1920
|
-
examples: [
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1626
|
+
examples: [
|
|
1627
|
+
[
|
|
1628
|
+
{
|
|
1629
|
+
name: "User",
|
|
1630
|
+
content: {
|
|
1631
|
+
text: "Show me all teams"
|
|
1632
|
+
}
|
|
1633
|
+
},
|
|
1634
|
+
{
|
|
1635
|
+
name: "Assistant",
|
|
1636
|
+
content: {
|
|
1637
|
+
text: "I'll list all the teams in Linear for you.",
|
|
1638
|
+
actions: ["LIST_LINEAR_TEAMS"]
|
|
1639
|
+
}
|
|
1932
1640
|
}
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1641
|
+
],
|
|
1642
|
+
[
|
|
1643
|
+
{
|
|
1644
|
+
name: "User",
|
|
1645
|
+
content: {
|
|
1646
|
+
text: "Which engineering teams do we have?"
|
|
1647
|
+
}
|
|
1648
|
+
},
|
|
1649
|
+
{
|
|
1650
|
+
name: "Assistant",
|
|
1651
|
+
content: {
|
|
1652
|
+
text: "Let me find the engineering teams for you.",
|
|
1653
|
+
actions: ["LIST_LINEAR_TEAMS"]
|
|
1654
|
+
}
|
|
1946
1655
|
}
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1656
|
+
],
|
|
1657
|
+
[
|
|
1658
|
+
{
|
|
1659
|
+
name: "User",
|
|
1660
|
+
content: {
|
|
1661
|
+
text: "Show me the teams I'm part of"
|
|
1662
|
+
}
|
|
1663
|
+
},
|
|
1664
|
+
{
|
|
1665
|
+
name: "Assistant",
|
|
1666
|
+
content: {
|
|
1667
|
+
text: "I'll show you the teams you're a member of.",
|
|
1668
|
+
actions: ["LIST_LINEAR_TEAMS"]
|
|
1669
|
+
}
|
|
1960
1670
|
}
|
|
1961
|
-
|
|
1962
|
-
]
|
|
1671
|
+
]
|
|
1672
|
+
],
|
|
1963
1673
|
async validate(runtime, _message, _state) {
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
return !!apiKey;
|
|
1967
|
-
} catch {
|
|
1968
|
-
return false;
|
|
1969
|
-
}
|
|
1674
|
+
const apiKey = runtime.getSetting("LINEAR_API_KEY");
|
|
1675
|
+
return !!apiKey;
|
|
1970
1676
|
},
|
|
1971
1677
|
async handler(runtime, message, _state, _options, callback) {
|
|
1972
1678
|
try {
|
|
@@ -1998,9 +1704,7 @@ var listTeamsAction = {
|
|
|
1998
1704
|
}
|
|
1999
1705
|
let teams = await linearService.getTeams();
|
|
2000
1706
|
if (specificTeam) {
|
|
2001
|
-
teams = teams.filter(
|
|
2002
|
-
(team) => team.key.toLowerCase() === specificTeam.toLowerCase() || team.name.toLowerCase() === specificTeam.toLowerCase()
|
|
2003
|
-
);
|
|
1707
|
+
teams = teams.filter((team) => team.key.toLowerCase() === specificTeam.toLowerCase() || team.name.toLowerCase() === specificTeam.toLowerCase());
|
|
2004
1708
|
}
|
|
2005
1709
|
if (nameFilter && !specificTeam) {
|
|
2006
1710
|
const keywords = nameFilter.toLowerCase().split(/\s+/);
|
|
@@ -2011,10 +1715,11 @@ var listTeamsAction = {
|
|
|
2011
1715
|
}
|
|
2012
1716
|
if (myTeams) {
|
|
2013
1717
|
try {
|
|
2014
|
-
const
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
1718
|
+
const userTeams = await linearService.getUserTeams();
|
|
1719
|
+
const userTeamIds = new Set(userTeams.map((t) => t.id));
|
|
1720
|
+
teams = teams.filter((team) => userTeamIds.has(team.id));
|
|
1721
|
+
} catch (error) {
|
|
1722
|
+
logger8.warn("Could not filter for user's teams:", error);
|
|
2018
1723
|
}
|
|
2019
1724
|
}
|
|
2020
1725
|
if (teams.length === 0) {
|
|
@@ -2038,13 +1743,11 @@ var listTeamsAction = {
|
|
|
2038
1743
|
const members = await membersQuery.nodes;
|
|
2039
1744
|
const projectsQuery = await team.projects();
|
|
2040
1745
|
const projects = await projectsQuery.nodes;
|
|
2041
|
-
return {
|
|
2042
|
-
...team,
|
|
1746
|
+
return Object.assign(team, {
|
|
2043
1747
|
memberCount: members.length,
|
|
2044
1748
|
projectCount: projects.length,
|
|
2045
1749
|
membersList: specificTeam ? members.slice(0, 5) : []
|
|
2046
|
-
|
|
2047
|
-
};
|
|
1750
|
+
});
|
|
2048
1751
|
}));
|
|
2049
1752
|
}
|
|
2050
1753
|
const teamList = teamsWithDetails.map((team, index) => {
|
|
@@ -2055,16 +1758,19 @@ var listTeamsAction = {
|
|
|
2055
1758
|
}
|
|
2056
1759
|
if (includeDetails || specificTeam) {
|
|
2057
1760
|
info += `
|
|
2058
|
-
Members: ${team.memberCount} | Projects: ${team.projectCount}`;
|
|
2059
|
-
|
|
2060
|
-
|
|
1761
|
+
Members: ${team.memberCount ?? 0} | Projects: ${team.projectCount ?? 0}`;
|
|
1762
|
+
const membersList = team.membersList ?? [];
|
|
1763
|
+
if (specificTeam && membersList.length > 0) {
|
|
1764
|
+
const memberNames = membersList.map((m) => m.name).join(", ");
|
|
2061
1765
|
info += `
|
|
2062
|
-
Team members: ${memberNames}${team.memberCount > 5 ? " ..." : ""}`;
|
|
1766
|
+
Team members: ${memberNames}${(team.memberCount ?? 0) > 5 ? " ..." : ""}`;
|
|
2063
1767
|
}
|
|
2064
1768
|
}
|
|
2065
1769
|
return info;
|
|
2066
|
-
}).join(
|
|
2067
|
-
|
|
1770
|
+
}).join(`
|
|
1771
|
+
|
|
1772
|
+
`);
|
|
1773
|
+
const headerText = specificTeam && teams.length === 1 ? `\uD83D\uDCCB Team Details:` : nameFilter ? `\uD83D\uDCCB Found ${teams.length} team${teams.length === 1 ? "" : "s"} matching "${nameFilter}":` : `\uD83D\uDCCB Found ${teams.length} team${teams.length === 1 ? "" : "s"}:`;
|
|
2068
1774
|
const resultMessage = `${headerText}
|
|
2069
1775
|
|
|
2070
1776
|
${teamList}`;
|
|
@@ -2093,7 +1799,7 @@ ${teamList}`;
|
|
|
2093
1799
|
};
|
|
2094
1800
|
} catch (error) {
|
|
2095
1801
|
logger8.error("Failed to list teams:", error);
|
|
2096
|
-
const errorMessage =
|
|
1802
|
+
const errorMessage = `❌ Failed to list teams: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
2097
1803
|
await callback?.({
|
|
2098
1804
|
text: errorMessage,
|
|
2099
1805
|
source: message.content.source
|
|
@@ -2106,91 +1812,71 @@ ${teamList}`;
|
|
|
2106
1812
|
}
|
|
2107
1813
|
};
|
|
2108
1814
|
|
|
2109
|
-
// src/actions/
|
|
2110
|
-
import {
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
-
|
|
2120
|
-
-
|
|
2121
|
-
-
|
|
2122
|
-
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
{
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
}
|
|
2138
|
-
|
|
2139
|
-
Only include fields that are clearly mentioned.`;
|
|
2140
|
-
var listProjectsAction = {
|
|
2141
|
-
name: "LIST_LINEAR_PROJECTS",
|
|
2142
|
-
description: "List projects in Linear with optional filters",
|
|
2143
|
-
similes: ["list-linear-projects", "show-linear-projects", "get-linear-projects", "view-linear-projects"],
|
|
2144
|
-
examples: [[
|
|
2145
|
-
{
|
|
2146
|
-
name: "User",
|
|
2147
|
-
content: {
|
|
2148
|
-
text: "Show me all projects"
|
|
2149
|
-
}
|
|
2150
|
-
},
|
|
2151
|
-
{
|
|
2152
|
-
name: "Assistant",
|
|
2153
|
-
content: {
|
|
2154
|
-
text: "I'll list all the projects in Linear for you.",
|
|
2155
|
-
actions: ["LIST_LINEAR_PROJECTS"]
|
|
1815
|
+
// src/actions/searchIssues.ts
|
|
1816
|
+
import {
|
|
1817
|
+
logger as logger9,
|
|
1818
|
+
ModelType as ModelType8
|
|
1819
|
+
} from "@elizaos/core";
|
|
1820
|
+
var searchTemplate = searchIssuesTemplate;
|
|
1821
|
+
var searchIssuesAction = {
|
|
1822
|
+
name: "SEARCH_LINEAR_ISSUES",
|
|
1823
|
+
description: "Search for issues in Linear with various filters",
|
|
1824
|
+
similes: [
|
|
1825
|
+
"search-linear-issues",
|
|
1826
|
+
"find-linear-issues",
|
|
1827
|
+
"query-linear-issues",
|
|
1828
|
+
"list-linear-issues"
|
|
1829
|
+
],
|
|
1830
|
+
examples: [
|
|
1831
|
+
[
|
|
1832
|
+
{
|
|
1833
|
+
name: "User",
|
|
1834
|
+
content: {
|
|
1835
|
+
text: "Show me all open bugs"
|
|
1836
|
+
}
|
|
1837
|
+
},
|
|
1838
|
+
{
|
|
1839
|
+
name: "Assistant",
|
|
1840
|
+
content: {
|
|
1841
|
+
text: "I'll search for all open bug issues in Linear.",
|
|
1842
|
+
actions: ["SEARCH_LINEAR_ISSUES"]
|
|
1843
|
+
}
|
|
2156
1844
|
}
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
1845
|
+
],
|
|
1846
|
+
[
|
|
1847
|
+
{
|
|
1848
|
+
name: "User",
|
|
1849
|
+
content: {
|
|
1850
|
+
text: "What is John working on?"
|
|
1851
|
+
}
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
name: "Assistant",
|
|
1855
|
+
content: {
|
|
1856
|
+
text: "I'll find the issues assigned to John.",
|
|
1857
|
+
actions: ["SEARCH_LINEAR_ISSUES"]
|
|
1858
|
+
}
|
|
2170
1859
|
}
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
1860
|
+
],
|
|
1861
|
+
[
|
|
1862
|
+
{
|
|
1863
|
+
name: "User",
|
|
1864
|
+
content: {
|
|
1865
|
+
text: "Show me high priority issues created this week"
|
|
1866
|
+
}
|
|
1867
|
+
},
|
|
1868
|
+
{
|
|
1869
|
+
name: "Assistant",
|
|
1870
|
+
content: {
|
|
1871
|
+
text: "I'll search for high priority issues created this week.",
|
|
1872
|
+
actions: ["SEARCH_LINEAR_ISSUES"]
|
|
1873
|
+
}
|
|
2184
1874
|
}
|
|
2185
|
-
|
|
2186
|
-
]
|
|
1875
|
+
]
|
|
1876
|
+
],
|
|
2187
1877
|
async validate(runtime, _message, _state) {
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
return !!apiKey;
|
|
2191
|
-
} catch {
|
|
2192
|
-
return false;
|
|
2193
|
-
}
|
|
1878
|
+
const apiKey = runtime.getSetting("LINEAR_API_KEY");
|
|
1879
|
+
return !!apiKey;
|
|
2194
1880
|
},
|
|
2195
1881
|
async handler(runtime, message, _state, _options, callback) {
|
|
2196
1882
|
try {
|
|
@@ -2198,158 +1884,168 @@ var listProjectsAction = {
|
|
|
2198
1884
|
if (!linearService) {
|
|
2199
1885
|
throw new Error("Linear service not available");
|
|
2200
1886
|
}
|
|
2201
|
-
const content = message.content.text
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
1887
|
+
const content = message.content.text;
|
|
1888
|
+
if (!content) {
|
|
1889
|
+
const errorMessage = "Please provide search criteria for issues.";
|
|
1890
|
+
await callback?.({
|
|
1891
|
+
text: errorMessage,
|
|
1892
|
+
source: message.content.source
|
|
1893
|
+
});
|
|
1894
|
+
return {
|
|
1895
|
+
text: errorMessage,
|
|
1896
|
+
success: false
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
let filters = {};
|
|
1900
|
+
const params = _options?.parameters;
|
|
1901
|
+
if (params?.filters) {
|
|
1902
|
+
filters = params.filters;
|
|
1903
|
+
} else {
|
|
1904
|
+
const prompt = searchTemplate.replace("{{userMessage}}", content);
|
|
2207
1905
|
const response = await runtime.useModel(ModelType8.TEXT_LARGE, {
|
|
2208
1906
|
prompt
|
|
2209
1907
|
});
|
|
2210
|
-
if (response) {
|
|
1908
|
+
if (!response) {
|
|
1909
|
+
filters = { query: content };
|
|
1910
|
+
} else {
|
|
2211
1911
|
try {
|
|
2212
|
-
const
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
1912
|
+
const cleanedResponse = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
|
|
1913
|
+
const parsed = JSON.parse(cleanedResponse);
|
|
1914
|
+
filters = {
|
|
1915
|
+
query: parsed.query,
|
|
1916
|
+
limit: parsed.limit || 10
|
|
1917
|
+
};
|
|
1918
|
+
if (parsed.states && parsed.states.length > 0) {
|
|
1919
|
+
filters.state = parsed.states;
|
|
1920
|
+
}
|
|
1921
|
+
if (parsed.assignees && parsed.assignees.length > 0) {
|
|
1922
|
+
const processedAssignees = [];
|
|
1923
|
+
for (const assignee of parsed.assignees) {
|
|
1924
|
+
if (assignee.toLowerCase() === "me") {
|
|
1925
|
+
try {
|
|
1926
|
+
const currentUser = await linearService.getCurrentUser();
|
|
1927
|
+
processedAssignees.push(currentUser.email);
|
|
1928
|
+
} catch {
|
|
1929
|
+
logger9.warn('Could not resolve "me" to current user');
|
|
1930
|
+
}
|
|
1931
|
+
} else {
|
|
1932
|
+
processedAssignees.push(assignee);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
if (processedAssignees.length > 0) {
|
|
1936
|
+
filters.assignee = processedAssignees;
|
|
2221
1937
|
}
|
|
2222
1938
|
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
if (parsed.dateFilter || parsed.leadFilter) {
|
|
2226
|
-
logger9.info("Date and lead filters noted but not yet implemented");
|
|
1939
|
+
if (parsed.hasAssignee === false) {
|
|
1940
|
+
filters.query = filters.query ? `${filters.query} unassigned` : "unassigned";
|
|
2227
1941
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
1942
|
+
if (parsed.priorities && parsed.priorities.length > 0) {
|
|
1943
|
+
const priorityMap = {
|
|
1944
|
+
urgent: 1,
|
|
1945
|
+
high: 2,
|
|
1946
|
+
normal: 3,
|
|
1947
|
+
low: 4,
|
|
1948
|
+
"1": 1,
|
|
1949
|
+
"2": 2,
|
|
1950
|
+
"3": 3,
|
|
1951
|
+
"4": 4
|
|
1952
|
+
};
|
|
1953
|
+
const priorities = parsed.priorities.map((p) => priorityMap[p.toLowerCase()]).filter(Boolean);
|
|
1954
|
+
if (priorities.length > 0) {
|
|
1955
|
+
filters.priority = priorities;
|
|
2239
1956
|
}
|
|
2240
1957
|
}
|
|
2241
|
-
|
|
1958
|
+
if (parsed.teams && parsed.teams.length > 0) {
|
|
1959
|
+
filters.team = parsed.teams[0];
|
|
1960
|
+
}
|
|
1961
|
+
if (parsed.labels && parsed.labels.length > 0) {
|
|
1962
|
+
filters.label = parsed.labels;
|
|
1963
|
+
}
|
|
1964
|
+
Object.keys(filters).forEach((key) => {
|
|
1965
|
+
if (filters[key] === undefined) {
|
|
1966
|
+
delete filters[key];
|
|
1967
|
+
}
|
|
1968
|
+
});
|
|
1969
|
+
} catch (parseError) {
|
|
1970
|
+
logger9.error("Failed to parse search filters:", parseError);
|
|
1971
|
+
filters = { query: content };
|
|
2242
1972
|
}
|
|
2243
1973
|
}
|
|
2244
1974
|
}
|
|
2245
|
-
if (!
|
|
1975
|
+
if (!filters.team) {
|
|
2246
1976
|
const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
|
|
2247
1977
|
if (defaultTeamKey) {
|
|
2248
|
-
const
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
if (defaultTeam) {
|
|
2253
|
-
teamId = defaultTeam.id;
|
|
2254
|
-
logger9.info(`Applying default team filter for projects: ${defaultTeam.name} (${defaultTeam.key})`);
|
|
1978
|
+
const searchingAllIssues = content.toLowerCase().includes("all") && (content.toLowerCase().includes("issue") || content.toLowerCase().includes("bug") || content.toLowerCase().includes("task"));
|
|
1979
|
+
if (!searchingAllIssues) {
|
|
1980
|
+
filters.team = defaultTeamKey;
|
|
1981
|
+
logger9.info(`Applying default team filter: ${defaultTeamKey}`);
|
|
2255
1982
|
}
|
|
2256
1983
|
}
|
|
2257
1984
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
if (stateFilter === "active") {
|
|
2263
|
-
return state === "started" || state === "in progress" || !state;
|
|
2264
|
-
} else if (stateFilter === "planned") {
|
|
2265
|
-
return state === "planned" || state === "backlog";
|
|
2266
|
-
} else if (stateFilter === "completed") {
|
|
2267
|
-
return state === "completed" || state === "done" || state === "canceled";
|
|
2268
|
-
}
|
|
2269
|
-
return true;
|
|
2270
|
-
});
|
|
2271
|
-
}
|
|
2272
|
-
if (projects.length === 0) {
|
|
2273
|
-
const noProjectsMessage = teamId ? "No projects found for the specified team." : "No projects found in Linear.";
|
|
1985
|
+
filters.limit = params?.limit ?? filters.limit ?? 10;
|
|
1986
|
+
const issues = await linearService.searchIssues(filters);
|
|
1987
|
+
if (issues.length === 0) {
|
|
1988
|
+
const noResultsMessage = "No issues found matching your search criteria.";
|
|
2274
1989
|
await callback?.({
|
|
2275
|
-
text:
|
|
1990
|
+
text: noResultsMessage,
|
|
2276
1991
|
source: message.content.source
|
|
2277
1992
|
});
|
|
2278
1993
|
return {
|
|
2279
|
-
text:
|
|
1994
|
+
text: noResultsMessage,
|
|
2280
1995
|
success: true,
|
|
2281
1996
|
data: {
|
|
2282
|
-
|
|
1997
|
+
issues: [],
|
|
1998
|
+
filters: filters ? { ...filters } : undefined,
|
|
1999
|
+
count: 0
|
|
2283
2000
|
}
|
|
2284
2001
|
};
|
|
2285
2002
|
}
|
|
2286
|
-
const
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
};
|
|
2296
|
-
})
|
|
2297
|
-
);
|
|
2298
|
-
const projectList = projectsWithDetails.map((project, index) => {
|
|
2299
|
-
const teamNames = project.teamsList.map((t) => t.name).join(", ") || "No teams";
|
|
2300
|
-
const status = project.state || "Active";
|
|
2301
|
-
const progress = project.progress ? ` (${Math.round(project.progress * 100)}% complete)` : "";
|
|
2302
|
-
const lead = project.leadUser ? ` | Lead: ${project.leadUser.name}` : "";
|
|
2303
|
-
const dates = [];
|
|
2304
|
-
if (project.startDate) dates.push(`Start: ${new Date(project.startDate).toLocaleDateString()}`);
|
|
2305
|
-
if (project.targetDate) dates.push(`Due: ${new Date(project.targetDate).toLocaleDateString()}`);
|
|
2306
|
-
const dateInfo = dates.length > 0 ? `
|
|
2307
|
-
${dates.join(" | ")}` : "";
|
|
2308
|
-
return `${index + 1}. ${project.name}${project.description ? ` - ${project.description}` : ""}
|
|
2309
|
-
Status: ${status}${progress} | Teams: ${teamNames}${lead}${dateInfo}`;
|
|
2310
|
-
}).join("\n\n");
|
|
2311
|
-
const headerText = stateFilter && stateFilter !== "all" ? `\u{1F4C1} Found ${projects.length} ${stateFilter} project${projects.length === 1 ? "" : "s"}:` : `\u{1F4C1} Found ${projects.length} project${projects.length === 1 ? "" : "s"}:`;
|
|
2312
|
-
const resultMessage = `${headerText}
|
|
2003
|
+
const issueList = await Promise.all(issues.map(async (issue, index) => {
|
|
2004
|
+
const state = await issue.state;
|
|
2005
|
+
const assignee = await issue.assignee;
|
|
2006
|
+
const priorityLabels = ["", "Urgent", "High", "Normal", "Low"];
|
|
2007
|
+
const priority = priorityLabels[issue.priority || 0] || "No priority";
|
|
2008
|
+
return `${index + 1}. ${issue.identifier}: ${issue.title}
|
|
2009
|
+
Status: ${state?.name || "No state"} | Priority: ${priority} | Assignee: ${assignee?.name || "Unassigned"}`;
|
|
2010
|
+
}));
|
|
2011
|
+
const issueText = issueList.join(`
|
|
2313
2012
|
|
|
2314
|
-
|
|
2013
|
+
`);
|
|
2014
|
+
const resultMessage = `\uD83D\uDCCB Found ${issues.length} issue${issues.length === 1 ? "" : "s"}:
|
|
2015
|
+
|
|
2016
|
+
${issueText}`;
|
|
2315
2017
|
await callback?.({
|
|
2316
2018
|
text: resultMessage,
|
|
2317
2019
|
source: message.content.source
|
|
2318
2020
|
});
|
|
2319
2021
|
return {
|
|
2320
|
-
text: `Found ${
|
|
2022
|
+
text: `Found ${issues.length} issue${issues.length === 1 ? "" : "s"}`,
|
|
2321
2023
|
success: true,
|
|
2322
2024
|
data: {
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
name:
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
progress: p.progress,
|
|
2340
|
-
startDate: p.startDate,
|
|
2341
|
-
targetDate: p.targetDate
|
|
2025
|
+
issues: await Promise.all(issues.map(async (issue) => {
|
|
2026
|
+
const state = await issue.state;
|
|
2027
|
+
const assignee = await issue.assignee;
|
|
2028
|
+
const team = await issue.team;
|
|
2029
|
+
return {
|
|
2030
|
+
id: issue.id,
|
|
2031
|
+
identifier: issue.identifier,
|
|
2032
|
+
title: issue.title,
|
|
2033
|
+
url: issue.url,
|
|
2034
|
+
priority: issue.priority,
|
|
2035
|
+
state: state ? { name: state.name, type: state.type } : null,
|
|
2036
|
+
assignee: assignee ? { name: assignee.name, email: assignee.email } : null,
|
|
2037
|
+
team: team ? { name: team.name, key: team.key } : null,
|
|
2038
|
+
createdAt: issue.createdAt instanceof Date ? issue.createdAt.toISOString() : issue.createdAt,
|
|
2039
|
+
updatedAt: issue.updatedAt instanceof Date ? issue.updatedAt.toISOString() : issue.updatedAt
|
|
2040
|
+
};
|
|
2342
2041
|
})),
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
team: teamId,
|
|
2346
|
-
state: stateFilter
|
|
2347
|
-
}
|
|
2042
|
+
filters: filters ? { ...filters } : undefined,
|
|
2043
|
+
count: issues.length
|
|
2348
2044
|
}
|
|
2349
2045
|
};
|
|
2350
2046
|
} catch (error) {
|
|
2351
|
-
logger9.error("Failed to
|
|
2352
|
-
const errorMessage =
|
|
2047
|
+
logger9.error("Failed to search issues:", error);
|
|
2048
|
+
const errorMessage = `❌ Failed to search issues: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
2353
2049
|
await callback?.({
|
|
2354
2050
|
text: errorMessage,
|
|
2355
2051
|
source: message.content.source
|
|
@@ -2362,90 +2058,71 @@ ${projectList}`;
|
|
|
2362
2058
|
}
|
|
2363
2059
|
};
|
|
2364
2060
|
|
|
2365
|
-
// src/actions/
|
|
2366
|
-
import {
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
-
|
|
2375
|
-
|
|
2376
|
-
-
|
|
2377
|
-
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
Only include fields that are clearly mentioned.`;
|
|
2395
|
-
var getActivityAction = {
|
|
2396
|
-
name: "GET_LINEAR_ACTIVITY",
|
|
2397
|
-
description: "Get recent Linear activity log with optional filters",
|
|
2398
|
-
similes: ["get-linear-activity", "show-linear-activity", "view-linear-activity", "check-linear-activity"],
|
|
2399
|
-
examples: [[
|
|
2400
|
-
{
|
|
2401
|
-
name: "User",
|
|
2402
|
-
content: {
|
|
2403
|
-
text: "Show me recent Linear activity"
|
|
2404
|
-
}
|
|
2405
|
-
},
|
|
2406
|
-
{
|
|
2407
|
-
name: "Assistant",
|
|
2408
|
-
content: {
|
|
2409
|
-
text: "I'll show you the recent Linear activity.",
|
|
2410
|
-
actions: ["GET_LINEAR_ACTIVITY"]
|
|
2061
|
+
// src/actions/updateIssue.ts
|
|
2062
|
+
import {
|
|
2063
|
+
logger as logger10,
|
|
2064
|
+
ModelType as ModelType9
|
|
2065
|
+
} from "@elizaos/core";
|
|
2066
|
+
var updateIssueAction = {
|
|
2067
|
+
name: "UPDATE_LINEAR_ISSUE",
|
|
2068
|
+
description: "Update an existing Linear issue",
|
|
2069
|
+
similes: [
|
|
2070
|
+
"update-linear-issue",
|
|
2071
|
+
"edit-linear-issue",
|
|
2072
|
+
"modify-linear-issue",
|
|
2073
|
+
"move-linear-issue",
|
|
2074
|
+
"change-linear-issue"
|
|
2075
|
+
],
|
|
2076
|
+
examples: [
|
|
2077
|
+
[
|
|
2078
|
+
{
|
|
2079
|
+
name: "User",
|
|
2080
|
+
content: {
|
|
2081
|
+
text: 'Update issue ENG-123 title to "Fix login button on all devices"'
|
|
2082
|
+
}
|
|
2083
|
+
},
|
|
2084
|
+
{
|
|
2085
|
+
name: "Assistant",
|
|
2086
|
+
content: {
|
|
2087
|
+
text: "I'll update the title of issue ENG-123 for you.",
|
|
2088
|
+
actions: ["UPDATE_LINEAR_ISSUE"]
|
|
2089
|
+
}
|
|
2411
2090
|
}
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2091
|
+
],
|
|
2092
|
+
[
|
|
2093
|
+
{
|
|
2094
|
+
name: "User",
|
|
2095
|
+
content: {
|
|
2096
|
+
text: "Move issue COM2-7 to the ELIZA team"
|
|
2097
|
+
}
|
|
2098
|
+
},
|
|
2099
|
+
{
|
|
2100
|
+
name: "Assistant",
|
|
2101
|
+
content: {
|
|
2102
|
+
text: "I'll move issue COM2-7 to the ELIZA team.",
|
|
2103
|
+
actions: ["UPDATE_LINEAR_ISSUE"]
|
|
2104
|
+
}
|
|
2425
2105
|
}
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2106
|
+
],
|
|
2107
|
+
[
|
|
2108
|
+
{
|
|
2109
|
+
name: "User",
|
|
2110
|
+
content: {
|
|
2111
|
+
text: "Change the priority of BUG-456 to high and assign to john@example.com"
|
|
2112
|
+
}
|
|
2113
|
+
},
|
|
2114
|
+
{
|
|
2115
|
+
name: "Assistant",
|
|
2116
|
+
content: {
|
|
2117
|
+
text: "I'll change the priority of BUG-456 to high and assign it to john@example.com.",
|
|
2118
|
+
actions: ["UPDATE_LINEAR_ISSUE"]
|
|
2119
|
+
}
|
|
2439
2120
|
}
|
|
2440
|
-
|
|
2441
|
-
]
|
|
2121
|
+
]
|
|
2122
|
+
],
|
|
2442
2123
|
async validate(runtime, _message, _state) {
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
return !!apiKey;
|
|
2446
|
-
} catch {
|
|
2447
|
-
return false;
|
|
2448
|
-
}
|
|
2124
|
+
const apiKey = runtime.getSetting("LINEAR_API_KEY");
|
|
2125
|
+
return !!apiKey;
|
|
2449
2126
|
},
|
|
2450
2127
|
async handler(runtime, message, _state, _options, callback) {
|
|
2451
2128
|
try {
|
|
@@ -2453,115 +2130,176 @@ var getActivityAction = {
|
|
|
2453
2130
|
if (!linearService) {
|
|
2454
2131
|
throw new Error("Linear service not available");
|
|
2455
2132
|
}
|
|
2456
|
-
const content = message.content.text
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
prompt
|
|
2133
|
+
const content = message.content.text;
|
|
2134
|
+
if (!content) {
|
|
2135
|
+
const errorMessage = "Please provide update instructions for the issue.";
|
|
2136
|
+
await callback?.({
|
|
2137
|
+
text: errorMessage,
|
|
2138
|
+
source: message.content.source
|
|
2463
2139
|
});
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2140
|
+
return {
|
|
2141
|
+
text: errorMessage,
|
|
2142
|
+
success: false
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
const prompt = updateIssueTemplate.replace("{{userMessage}}", content);
|
|
2146
|
+
const response = await runtime.useModel(ModelType9.TEXT_LARGE, {
|
|
2147
|
+
prompt
|
|
2148
|
+
});
|
|
2149
|
+
if (!response) {
|
|
2150
|
+
throw new Error("Failed to extract update information");
|
|
2151
|
+
}
|
|
2152
|
+
let issueId;
|
|
2153
|
+
const updates = {};
|
|
2154
|
+
try {
|
|
2155
|
+
const cleanedResponse = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
|
|
2156
|
+
const parsed = JSON.parse(cleanedResponse);
|
|
2157
|
+
issueId = parsed.issueId;
|
|
2158
|
+
if (!issueId) {
|
|
2159
|
+
throw new Error("Issue ID not found in parsed response");
|
|
2160
|
+
}
|
|
2161
|
+
if (parsed.updates?.title) {
|
|
2162
|
+
updates.title = parsed.updates.title;
|
|
2163
|
+
}
|
|
2164
|
+
if (parsed.updates?.description) {
|
|
2165
|
+
updates.description = parsed.updates.description;
|
|
2166
|
+
}
|
|
2167
|
+
if (parsed.updates?.priority) {
|
|
2168
|
+
updates.priority = Number(parsed.updates.priority);
|
|
2169
|
+
}
|
|
2170
|
+
if (parsed.updates?.teamKey) {
|
|
2171
|
+
const teams = await linearService.getTeams();
|
|
2172
|
+
const team = teams.find((t) => t.key.toLowerCase() === parsed.updates.teamKey.toLowerCase());
|
|
2173
|
+
if (team) {
|
|
2174
|
+
updates.teamId = team.id;
|
|
2175
|
+
logger10.info(`Moving issue to team: ${team.name} (${team.key})`);
|
|
2176
|
+
} else {
|
|
2177
|
+
logger10.warn(`Team with key ${parsed.updates.teamKey} not found`);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
if (parsed.updates?.assignee) {
|
|
2181
|
+
const cleanAssignee = parsed.updates.assignee.replace(/^@/, "");
|
|
2182
|
+
const users = await linearService.getUsers();
|
|
2183
|
+
const user = users.find((u) => u.email === cleanAssignee || u.name.toLowerCase().includes(cleanAssignee.toLowerCase()));
|
|
2184
|
+
if (user) {
|
|
2185
|
+
updates.assigneeId = user.id;
|
|
2186
|
+
} else {
|
|
2187
|
+
logger10.warn(`User ${cleanAssignee} not found`);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
if (parsed.updates?.status) {
|
|
2191
|
+
const issue = await linearService.getIssue(issueId);
|
|
2192
|
+
const issueTeam = await issue.team;
|
|
2193
|
+
const teamId = updates.teamId || issueTeam?.id;
|
|
2194
|
+
if (!teamId) {
|
|
2195
|
+
logger10.warn("Could not determine team for status update");
|
|
2196
|
+
} else {
|
|
2197
|
+
const states = await linearService.getWorkflowStates(teamId);
|
|
2198
|
+
const state = states.find((s) => s.name.toLowerCase() === parsed.updates.status.toLowerCase() || s.type.toLowerCase() === parsed.updates.status.toLowerCase());
|
|
2199
|
+
if (state) {
|
|
2200
|
+
updates.stateId = state.id;
|
|
2201
|
+
logger10.info(`Changing status to: ${state.name}`);
|
|
2202
|
+
} else {
|
|
2203
|
+
logger10.warn(`Status ${parsed.updates.status} not found for team`);
|
|
2506
2204
|
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
if (parsed.updates?.labels && Array.isArray(parsed.updates.labels)) {
|
|
2208
|
+
const teamId = updates.teamId;
|
|
2209
|
+
const labels = await linearService.getLabels(teamId);
|
|
2210
|
+
const labelIds = [];
|
|
2211
|
+
for (const labelName of parsed.updates.labels) {
|
|
2212
|
+
if (labelName) {
|
|
2213
|
+
const label = labels.find((l) => l.name.toLowerCase() === labelName.toLowerCase());
|
|
2214
|
+
if (label) {
|
|
2215
|
+
labelIds.push(label.id);
|
|
2216
|
+
}
|
|
2509
2217
|
}
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2218
|
+
}
|
|
2219
|
+
updates.labelIds = labelIds;
|
|
2220
|
+
}
|
|
2221
|
+
} catch (parseError) {
|
|
2222
|
+
logger10.warn("Failed to parse LLM response, falling back to regex parsing:", parseError);
|
|
2223
|
+
const issueMatch = content.match(/(\w+-\d+)/);
|
|
2224
|
+
if (!issueMatch) {
|
|
2225
|
+
const errorMessage = "Please specify an issue ID (e.g., ENG-123) to update.";
|
|
2226
|
+
await callback?.({
|
|
2227
|
+
text: errorMessage,
|
|
2228
|
+
source: message.content.source
|
|
2229
|
+
});
|
|
2230
|
+
return {
|
|
2231
|
+
text: errorMessage,
|
|
2232
|
+
success: false
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
issueId = issueMatch[1];
|
|
2236
|
+
const titleMatch = content.match(/title to ["'](.+?)["']/i);
|
|
2237
|
+
if (titleMatch) {
|
|
2238
|
+
updates.title = titleMatch[1];
|
|
2239
|
+
}
|
|
2240
|
+
const priorityMatch = content.match(/priority (?:to |as )?(\w+)/i);
|
|
2241
|
+
if (priorityMatch) {
|
|
2242
|
+
const priorityMap = {
|
|
2243
|
+
urgent: 1,
|
|
2244
|
+
high: 2,
|
|
2245
|
+
normal: 3,
|
|
2246
|
+
medium: 3,
|
|
2247
|
+
low: 4
|
|
2248
|
+
};
|
|
2249
|
+
const priority = priorityMap[priorityMatch[1].toLowerCase()];
|
|
2250
|
+
if (priority) {
|
|
2251
|
+
updates.priority = priority;
|
|
2513
2252
|
}
|
|
2514
2253
|
}
|
|
2515
2254
|
}
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
const fromTime = new Date(filters.fromDate).getTime();
|
|
2519
|
-
activity = activity.filter((item) => new Date(item.timestamp).getTime() >= fromTime);
|
|
2520
|
-
}
|
|
2521
|
-
activity = activity.slice(0, limit);
|
|
2522
|
-
if (activity.length === 0) {
|
|
2523
|
-
const noActivityMessage = filters.fromDate ? `No Linear activity found for the specified filters.` : "No recent Linear activity found.";
|
|
2255
|
+
if (Object.keys(updates).length === 0) {
|
|
2256
|
+
const errorMessage = `No valid updates found. Please specify what to update (e.g., "Update issue ENG-123 title to 'New Title'")`;
|
|
2524
2257
|
await callback?.({
|
|
2525
|
-
text:
|
|
2258
|
+
text: errorMessage,
|
|
2526
2259
|
source: message.content.source
|
|
2527
2260
|
});
|
|
2528
2261
|
return {
|
|
2529
|
-
text:
|
|
2530
|
-
success:
|
|
2531
|
-
data: {
|
|
2532
|
-
activity: []
|
|
2533
|
-
}
|
|
2262
|
+
text: errorMessage,
|
|
2263
|
+
success: false
|
|
2534
2264
|
};
|
|
2535
2265
|
}
|
|
2536
|
-
const
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2266
|
+
const updatedIssue = await linearService.updateIssue(issueId, updates);
|
|
2267
|
+
const updateSummary = [];
|
|
2268
|
+
if (updates.title)
|
|
2269
|
+
updateSummary.push(`title: "${updates.title}"`);
|
|
2270
|
+
if (updates.priority)
|
|
2271
|
+
updateSummary.push(`priority: ${["", "urgent", "high", "normal", "low"][updates.priority]}`);
|
|
2272
|
+
if (updates.teamId)
|
|
2273
|
+
updateSummary.push(`moved to team`);
|
|
2274
|
+
if (updates.assigneeId)
|
|
2275
|
+
updateSummary.push(`assigned to user`);
|
|
2276
|
+
if (updates.stateId)
|
|
2277
|
+
updateSummary.push(`status changed`);
|
|
2278
|
+
if (updates.labelIds)
|
|
2279
|
+
updateSummary.push(`labels updated`);
|
|
2280
|
+
const successMessage = `✅ Updated issue ${updatedIssue.identifier}: ${updateSummary.join(", ")}
|
|
2547
2281
|
|
|
2548
|
-
${
|
|
2282
|
+
View it at: ${updatedIssue.url}`;
|
|
2549
2283
|
await callback?.({
|
|
2550
|
-
text:
|
|
2284
|
+
text: successMessage,
|
|
2551
2285
|
source: message.content.source
|
|
2552
2286
|
});
|
|
2553
2287
|
return {
|
|
2554
|
-
text: `
|
|
2288
|
+
text: `Updated issue ${updatedIssue.identifier}: ${updateSummary.join(", ")}`,
|
|
2555
2289
|
success: true,
|
|
2556
2290
|
data: {
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2291
|
+
issueId: updatedIssue.id,
|
|
2292
|
+
identifier: updatedIssue.identifier,
|
|
2293
|
+
updates: updates ? Object.fromEntries(Object.entries(updates).map(([key, value]) => [
|
|
2294
|
+
key,
|
|
2295
|
+
value instanceof Date ? value.toISOString() : value
|
|
2296
|
+
])) : undefined,
|
|
2297
|
+
url: updatedIssue.url
|
|
2560
2298
|
}
|
|
2561
2299
|
};
|
|
2562
2300
|
} catch (error) {
|
|
2563
|
-
logger10.error("Failed to
|
|
2564
|
-
const errorMessage =
|
|
2301
|
+
logger10.error("Failed to update issue:", error);
|
|
2302
|
+
const errorMessage = `❌ Failed to update issue: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
2565
2303
|
await callback?.({
|
|
2566
2304
|
text: errorMessage,
|
|
2567
2305
|
source: message.content.source
|
|
@@ -2574,83 +2312,498 @@ ${activityText}`;
|
|
|
2574
2312
|
}
|
|
2575
2313
|
};
|
|
2576
2314
|
|
|
2577
|
-
// src/
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
}
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
actions: ["CLEAR_LINEAR_ACTIVITY"]
|
|
2315
|
+
// src/providers/activity.ts
|
|
2316
|
+
var linearActivityProvider = {
|
|
2317
|
+
name: "LINEAR_ACTIVITY",
|
|
2318
|
+
description: "Provides context about recent Linear activity",
|
|
2319
|
+
get: async (runtime, _message, _state) => {
|
|
2320
|
+
try {
|
|
2321
|
+
const linearService = runtime.getService("linear");
|
|
2322
|
+
if (!linearService) {
|
|
2323
|
+
return {
|
|
2324
|
+
text: "Linear service is not available"
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
const activity = linearService.getActivityLog(10);
|
|
2328
|
+
if (activity.length === 0) {
|
|
2329
|
+
return {
|
|
2330
|
+
text: "No recent Linear activity"
|
|
2331
|
+
};
|
|
2595
2332
|
}
|
|
2333
|
+
const activityList = activity.map((item) => {
|
|
2334
|
+
const status = item.success ? "✓" : "✗";
|
|
2335
|
+
const time = new Date(item.timestamp).toLocaleTimeString();
|
|
2336
|
+
return `${status} ${time}: ${item.action} ${item.resource_type} ${item.resource_id}`;
|
|
2337
|
+
});
|
|
2338
|
+
const text = `Recent Linear Activity:
|
|
2339
|
+
${activityList.join(`
|
|
2340
|
+
`)}`;
|
|
2341
|
+
return {
|
|
2342
|
+
text,
|
|
2343
|
+
data: {
|
|
2344
|
+
activity: activity.slice(0, 10).map((item) => ({
|
|
2345
|
+
id: item.id,
|
|
2346
|
+
action: item.action,
|
|
2347
|
+
resource_type: item.resource_type,
|
|
2348
|
+
resource_id: item.resource_id,
|
|
2349
|
+
success: item.success,
|
|
2350
|
+
error: item.error,
|
|
2351
|
+
details: JSON.stringify(item.details),
|
|
2352
|
+
timestamp: typeof item.timestamp === "string" ? item.timestamp : new Date(item.timestamp).toISOString()
|
|
2353
|
+
}))
|
|
2354
|
+
}
|
|
2355
|
+
};
|
|
2356
|
+
} catch (_error) {
|
|
2357
|
+
return {
|
|
2358
|
+
text: "Error retrieving Linear activity"
|
|
2359
|
+
};
|
|
2596
2360
|
}
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
|
|
2364
|
+
// src/providers/issues.ts
|
|
2365
|
+
var linearIssuesProvider = {
|
|
2366
|
+
name: "LINEAR_ISSUES",
|
|
2367
|
+
description: "Provides context about recent Linear issues",
|
|
2368
|
+
get: async (runtime, _message, _state) => {
|
|
2369
|
+
try {
|
|
2370
|
+
const linearService = runtime.getService("linear");
|
|
2371
|
+
if (!linearService) {
|
|
2372
|
+
return {
|
|
2373
|
+
text: "Linear service is not available"
|
|
2374
|
+
};
|
|
2375
|
+
}
|
|
2376
|
+
const issues = await linearService.searchIssues({ limit: 10 });
|
|
2377
|
+
if (issues.length === 0) {
|
|
2378
|
+
return {
|
|
2379
|
+
text: "No recent Linear issues found"
|
|
2380
|
+
};
|
|
2609
2381
|
}
|
|
2382
|
+
const issuesList = await Promise.all(issues.map(async (issue) => {
|
|
2383
|
+
const [assignee, state] = await Promise.all([issue.assignee, issue.state]);
|
|
2384
|
+
return `- ${issue.identifier}: ${issue.title} (${state?.name || "Unknown"}, ${assignee?.name || "Unassigned"})`;
|
|
2385
|
+
}));
|
|
2386
|
+
const text = `Recent Linear Issues:
|
|
2387
|
+
${issuesList.join(`
|
|
2388
|
+
`)}`;
|
|
2389
|
+
return {
|
|
2390
|
+
text,
|
|
2391
|
+
data: {
|
|
2392
|
+
issues: issues.map((issue) => ({
|
|
2393
|
+
id: issue.id,
|
|
2394
|
+
identifier: issue.identifier,
|
|
2395
|
+
title: issue.title
|
|
2396
|
+
}))
|
|
2397
|
+
}
|
|
2398
|
+
};
|
|
2399
|
+
} catch (_error) {
|
|
2400
|
+
return {
|
|
2401
|
+
text: "Error retrieving Linear issues"
|
|
2402
|
+
};
|
|
2610
2403
|
}
|
|
2611
|
-
|
|
2612
|
-
|
|
2404
|
+
}
|
|
2405
|
+
};
|
|
2406
|
+
|
|
2407
|
+
// src/providers/projects.ts
|
|
2408
|
+
var linearProjectsProvider = {
|
|
2409
|
+
name: "LINEAR_PROJECTS",
|
|
2410
|
+
description: "Provides context about active Linear projects",
|
|
2411
|
+
get: async (runtime, _message, _state) => {
|
|
2613
2412
|
try {
|
|
2614
|
-
const
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2413
|
+
const linearService = runtime.getService("linear");
|
|
2414
|
+
if (!linearService) {
|
|
2415
|
+
return {
|
|
2416
|
+
text: "Linear service is not available"
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
const projects = await linearService.getProjects();
|
|
2420
|
+
if (projects.length === 0) {
|
|
2421
|
+
return {
|
|
2422
|
+
text: "No Linear projects found"
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
const activeProjects = projects.filter((project) => project.state === "started" || project.state === "planned");
|
|
2426
|
+
const projectsList = activeProjects.slice(0, 10).map((project) => `- ${project.name}: ${project.state} (${project.startDate || "No start date"} - ${project.targetDate || "No target date"})`);
|
|
2427
|
+
const text = `Active Linear Projects:
|
|
2428
|
+
${projectsList.join(`
|
|
2429
|
+
`)}`;
|
|
2430
|
+
return {
|
|
2431
|
+
text,
|
|
2432
|
+
data: {
|
|
2433
|
+
projects: activeProjects.slice(0, 10).map((project) => ({
|
|
2434
|
+
id: project.id,
|
|
2435
|
+
name: project.name,
|
|
2436
|
+
state: project.state
|
|
2437
|
+
}))
|
|
2438
|
+
}
|
|
2439
|
+
};
|
|
2440
|
+
} catch (_error) {
|
|
2441
|
+
return {
|
|
2442
|
+
text: "Error retrieving Linear projects"
|
|
2443
|
+
};
|
|
2618
2444
|
}
|
|
2619
|
-
}
|
|
2620
|
-
|
|
2445
|
+
}
|
|
2446
|
+
};
|
|
2447
|
+
|
|
2448
|
+
// src/providers/teams.ts
|
|
2449
|
+
var linearTeamsProvider = {
|
|
2450
|
+
name: "LINEAR_TEAMS",
|
|
2451
|
+
description: "Provides context about Linear teams",
|
|
2452
|
+
get: async (runtime, _message, _state) => {
|
|
2621
2453
|
try {
|
|
2622
2454
|
const linearService = runtime.getService("linear");
|
|
2623
2455
|
if (!linearService) {
|
|
2624
|
-
|
|
2456
|
+
return {
|
|
2457
|
+
text: "Linear service is not available"
|
|
2458
|
+
};
|
|
2625
2459
|
}
|
|
2626
|
-
await linearService.
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
}
|
|
2460
|
+
const teams = await linearService.getTeams();
|
|
2461
|
+
if (teams.length === 0) {
|
|
2462
|
+
return {
|
|
2463
|
+
text: "No Linear teams found"
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
const teamsList = teams.map((team) => `- ${team.name} (${team.key}): ${team.description || "No description"}`);
|
|
2467
|
+
const text = `Linear Teams:
|
|
2468
|
+
${teamsList.join(`
|
|
2469
|
+
`)}`;
|
|
2632
2470
|
return {
|
|
2633
|
-
text
|
|
2634
|
-
|
|
2471
|
+
text,
|
|
2472
|
+
data: {
|
|
2473
|
+
teams: teams.map((team) => ({
|
|
2474
|
+
id: team.id,
|
|
2475
|
+
name: team.name,
|
|
2476
|
+
key: team.key
|
|
2477
|
+
}))
|
|
2478
|
+
}
|
|
2635
2479
|
};
|
|
2636
|
-
} catch (
|
|
2637
|
-
logger11.error("Failed to clear Linear activity:", error);
|
|
2638
|
-
const errorMessage = `\u274C Failed to clear Linear activity: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
2639
|
-
await callback?.({
|
|
2640
|
-
text: errorMessage,
|
|
2641
|
-
source: message.content.source
|
|
2642
|
-
});
|
|
2480
|
+
} catch (_error) {
|
|
2643
2481
|
return {
|
|
2644
|
-
text:
|
|
2645
|
-
success: false
|
|
2482
|
+
text: "Error retrieving Linear teams"
|
|
2646
2483
|
};
|
|
2647
2484
|
}
|
|
2648
2485
|
}
|
|
2649
2486
|
};
|
|
2650
2487
|
|
|
2488
|
+
// src/services/linear.ts
|
|
2489
|
+
import { logger as logger11, Service } from "@elizaos/core";
|
|
2490
|
+
import {
|
|
2491
|
+
LinearClient
|
|
2492
|
+
} from "@linear/sdk";
|
|
2493
|
+
|
|
2494
|
+
// src/types/index.ts
|
|
2495
|
+
class LinearAPIError extends Error {
|
|
2496
|
+
status;
|
|
2497
|
+
response;
|
|
2498
|
+
constructor(message, status, response) {
|
|
2499
|
+
super(message);
|
|
2500
|
+
this.status = status;
|
|
2501
|
+
this.response = response;
|
|
2502
|
+
this.name = "LinearAPIError";
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
class LinearAuthenticationError extends LinearAPIError {
|
|
2507
|
+
constructor(message) {
|
|
2508
|
+
super(message, 401);
|
|
2509
|
+
this.name = "LinearAuthenticationError";
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
// src/services/linear.ts
|
|
2514
|
+
class LinearService extends Service {
|
|
2515
|
+
static serviceType = "linear";
|
|
2516
|
+
capabilityDescription = "Linear API integration for issue tracking, project management, and team collaboration";
|
|
2517
|
+
client;
|
|
2518
|
+
activityLog = [];
|
|
2519
|
+
linearConfig;
|
|
2520
|
+
workspaceId;
|
|
2521
|
+
constructor(runtime) {
|
|
2522
|
+
super(runtime);
|
|
2523
|
+
const apiKey = runtime?.getSetting("LINEAR_API_KEY");
|
|
2524
|
+
const workspaceId = runtime?.getSetting("LINEAR_WORKSPACE_ID");
|
|
2525
|
+
if (!apiKey) {
|
|
2526
|
+
throw new LinearAuthenticationError("Linear API key is required");
|
|
2527
|
+
}
|
|
2528
|
+
this.linearConfig = {
|
|
2529
|
+
LINEAR_API_KEY: apiKey,
|
|
2530
|
+
LINEAR_WORKSPACE_ID: workspaceId
|
|
2531
|
+
};
|
|
2532
|
+
this.workspaceId = workspaceId;
|
|
2533
|
+
this.client = new LinearClient({
|
|
2534
|
+
apiKey: this.linearConfig.LINEAR_API_KEY
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2537
|
+
static async start(runtime) {
|
|
2538
|
+
const service = new LinearService(runtime);
|
|
2539
|
+
await service.validateConnection();
|
|
2540
|
+
logger11.info("Linear service started successfully");
|
|
2541
|
+
return service;
|
|
2542
|
+
}
|
|
2543
|
+
async stop() {
|
|
2544
|
+
this.activityLog = [];
|
|
2545
|
+
logger11.info("Linear service stopped");
|
|
2546
|
+
}
|
|
2547
|
+
async validateConnection() {
|
|
2548
|
+
try {
|
|
2549
|
+
const viewer = await this.client.viewer;
|
|
2550
|
+
logger11.info(`Linear connected as user: ${viewer.email}`);
|
|
2551
|
+
} catch (_error) {
|
|
2552
|
+
throw new LinearAuthenticationError("Failed to authenticate with Linear API");
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
logActivity(action, resourceType, resourceId, details, success, error) {
|
|
2556
|
+
const activity = {
|
|
2557
|
+
id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
2558
|
+
timestamp: new Date().toISOString(),
|
|
2559
|
+
action,
|
|
2560
|
+
resource_type: resourceType,
|
|
2561
|
+
resource_id: resourceId,
|
|
2562
|
+
details,
|
|
2563
|
+
success,
|
|
2564
|
+
error
|
|
2565
|
+
};
|
|
2566
|
+
this.activityLog.push(activity);
|
|
2567
|
+
if (this.activityLog.length > 1000) {
|
|
2568
|
+
this.activityLog = this.activityLog.slice(-1000);
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
getActivityLog(limit, filter) {
|
|
2572
|
+
let filtered = [...this.activityLog];
|
|
2573
|
+
if (filter) {
|
|
2574
|
+
filtered = filtered.filter((item) => {
|
|
2575
|
+
return Object.entries(filter).every(([key, value]) => {
|
|
2576
|
+
return item[key] === value;
|
|
2577
|
+
});
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
return filtered.slice(-(limit || 100));
|
|
2581
|
+
}
|
|
2582
|
+
clearActivityLog() {
|
|
2583
|
+
this.activityLog = [];
|
|
2584
|
+
logger11.info("Linear activity log cleared");
|
|
2585
|
+
}
|
|
2586
|
+
async getTeams() {
|
|
2587
|
+
const teams = await this.client.teams();
|
|
2588
|
+
const teamList = await teams.nodes;
|
|
2589
|
+
this.logActivity("list_teams", "team", "all", { count: teamList.length }, true);
|
|
2590
|
+
return teamList;
|
|
2591
|
+
}
|
|
2592
|
+
async getTeam(teamId) {
|
|
2593
|
+
const team = await this.client.team(teamId);
|
|
2594
|
+
this.logActivity("get_team", "team", teamId, { name: team.name }, true);
|
|
2595
|
+
return team;
|
|
2596
|
+
}
|
|
2597
|
+
async createIssue(input) {
|
|
2598
|
+
const issuePayload = await this.client.createIssue({
|
|
2599
|
+
title: input.title,
|
|
2600
|
+
description: input.description,
|
|
2601
|
+
teamId: input.teamId,
|
|
2602
|
+
priority: input.priority,
|
|
2603
|
+
assigneeId: input.assigneeId,
|
|
2604
|
+
labelIds: input.labelIds,
|
|
2605
|
+
projectId: input.projectId,
|
|
2606
|
+
stateId: input.stateId,
|
|
2607
|
+
estimate: input.estimate,
|
|
2608
|
+
dueDate: input.dueDate
|
|
2609
|
+
});
|
|
2610
|
+
const issue = await issuePayload.issue;
|
|
2611
|
+
if (!issue) {
|
|
2612
|
+
throw new Error("Failed to create issue");
|
|
2613
|
+
}
|
|
2614
|
+
this.logActivity("create_issue", "issue", issue.id, {
|
|
2615
|
+
title: input.title,
|
|
2616
|
+
teamId: input.teamId
|
|
2617
|
+
}, true);
|
|
2618
|
+
return issue;
|
|
2619
|
+
}
|
|
2620
|
+
async getIssue(issueId) {
|
|
2621
|
+
const issue = await this.client.issue(issueId);
|
|
2622
|
+
this.logActivity("get_issue", "issue", issueId, {
|
|
2623
|
+
title: issue.title,
|
|
2624
|
+
identifier: issue.identifier
|
|
2625
|
+
}, true);
|
|
2626
|
+
return issue;
|
|
2627
|
+
}
|
|
2628
|
+
async updateIssue(issueId, updates) {
|
|
2629
|
+
const updatePayload = await this.client.updateIssue(issueId, {
|
|
2630
|
+
title: updates.title,
|
|
2631
|
+
description: updates.description,
|
|
2632
|
+
priority: updates.priority,
|
|
2633
|
+
assigneeId: updates.assigneeId,
|
|
2634
|
+
labelIds: updates.labelIds,
|
|
2635
|
+
projectId: updates.projectId,
|
|
2636
|
+
stateId: updates.stateId,
|
|
2637
|
+
estimate: updates.estimate,
|
|
2638
|
+
dueDate: updates.dueDate
|
|
2639
|
+
});
|
|
2640
|
+
const issue = await updatePayload.issue;
|
|
2641
|
+
if (!issue) {
|
|
2642
|
+
throw new Error("Failed to update issue");
|
|
2643
|
+
}
|
|
2644
|
+
this.logActivity("update_issue", "issue", issueId, updates, true);
|
|
2645
|
+
return issue;
|
|
2646
|
+
}
|
|
2647
|
+
async deleteIssue(issueId) {
|
|
2648
|
+
const archivePayload = await this.client.archiveIssue(issueId);
|
|
2649
|
+
const success = await archivePayload.success;
|
|
2650
|
+
if (!success) {
|
|
2651
|
+
throw new Error("Failed to archive issue");
|
|
2652
|
+
}
|
|
2653
|
+
this.logActivity("delete_issue", "issue", issueId, { action: "archived" }, true);
|
|
2654
|
+
}
|
|
2655
|
+
async searchIssues(filters) {
|
|
2656
|
+
const filterObject = {};
|
|
2657
|
+
if (filters.query) {
|
|
2658
|
+
filterObject.or = [
|
|
2659
|
+
{ title: { containsIgnoreCase: filters.query } },
|
|
2660
|
+
{ description: { containsIgnoreCase: filters.query } }
|
|
2661
|
+
];
|
|
2662
|
+
}
|
|
2663
|
+
if (filters.team) {
|
|
2664
|
+
const teams = await this.getTeams();
|
|
2665
|
+
const team = teams.find((t) => t.key.toLowerCase() === filters.team?.toLowerCase() || t.name.toLowerCase() === filters.team?.toLowerCase());
|
|
2666
|
+
if (team) {
|
|
2667
|
+
filterObject.team = { id: { eq: team.id } };
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
if (filters.assignee && filters.assignee.length > 0) {
|
|
2671
|
+
const users = await this.getUsers();
|
|
2672
|
+
const assigneeIds = filters.assignee.map((assigneeName) => {
|
|
2673
|
+
const user = users.find((u) => u.email === assigneeName || u.name.toLowerCase().includes(assigneeName.toLowerCase()));
|
|
2674
|
+
return user?.id;
|
|
2675
|
+
}).filter(Boolean);
|
|
2676
|
+
if (assigneeIds.length > 0) {
|
|
2677
|
+
filterObject.assignee = { id: { in: assigneeIds } };
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
if (filters.priority && filters.priority.length > 0) {
|
|
2681
|
+
filterObject.priority = { number: { in: filters.priority } };
|
|
2682
|
+
}
|
|
2683
|
+
if (filters.state && filters.state.length > 0) {
|
|
2684
|
+
filterObject.state = {
|
|
2685
|
+
name: { in: filters.state }
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
if (filters.label && filters.label.length > 0) {
|
|
2689
|
+
filterObject.labels = {
|
|
2690
|
+
some: {
|
|
2691
|
+
name: { in: filters.label }
|
|
2692
|
+
}
|
|
2693
|
+
};
|
|
2694
|
+
}
|
|
2695
|
+
const query = this.client.issues({
|
|
2696
|
+
first: filters.limit || 50,
|
|
2697
|
+
filter: Object.keys(filterObject).length > 0 ? filterObject : undefined
|
|
2698
|
+
});
|
|
2699
|
+
const issues = await query;
|
|
2700
|
+
const issueList = await issues.nodes;
|
|
2701
|
+
this.logActivity("search_issues", "issue", "search", {
|
|
2702
|
+
filters: { ...filters },
|
|
2703
|
+
count: issueList.length
|
|
2704
|
+
}, true);
|
|
2705
|
+
return issueList;
|
|
2706
|
+
}
|
|
2707
|
+
async createComment(input) {
|
|
2708
|
+
const commentPayload = await this.client.createComment({
|
|
2709
|
+
body: input.body,
|
|
2710
|
+
issueId: input.issueId
|
|
2711
|
+
});
|
|
2712
|
+
const comment = await commentPayload.comment;
|
|
2713
|
+
if (!comment) {
|
|
2714
|
+
throw new Error("Failed to create comment");
|
|
2715
|
+
}
|
|
2716
|
+
this.logActivity("create_comment", "comment", comment.id, {
|
|
2717
|
+
issueId: input.issueId,
|
|
2718
|
+
bodyLength: input.body.length
|
|
2719
|
+
}, true);
|
|
2720
|
+
return comment;
|
|
2721
|
+
}
|
|
2722
|
+
async getProjects(teamId) {
|
|
2723
|
+
const query = this.client.projects({
|
|
2724
|
+
first: 100
|
|
2725
|
+
});
|
|
2726
|
+
const projects = await query;
|
|
2727
|
+
let projectList = await projects.nodes;
|
|
2728
|
+
if (teamId) {
|
|
2729
|
+
const filteredProjects = await Promise.all(projectList.map(async (project) => {
|
|
2730
|
+
const projectTeams = await project.teams();
|
|
2731
|
+
const teamsList = await projectTeams.nodes;
|
|
2732
|
+
const hasTeam = teamsList.some((team) => team.id === teamId);
|
|
2733
|
+
return hasTeam ? project : null;
|
|
2734
|
+
}));
|
|
2735
|
+
projectList = filteredProjects.filter(Boolean);
|
|
2736
|
+
}
|
|
2737
|
+
this.logActivity("list_projects", "project", "all", {
|
|
2738
|
+
count: projectList.length,
|
|
2739
|
+
teamId
|
|
2740
|
+
}, true);
|
|
2741
|
+
return projectList;
|
|
2742
|
+
}
|
|
2743
|
+
async getProject(projectId) {
|
|
2744
|
+
const project = await this.client.project(projectId);
|
|
2745
|
+
this.logActivity("get_project", "project", projectId, {
|
|
2746
|
+
name: project.name
|
|
2747
|
+
}, true);
|
|
2748
|
+
return project;
|
|
2749
|
+
}
|
|
2750
|
+
async getUsers() {
|
|
2751
|
+
const users = await this.client.users();
|
|
2752
|
+
const userList = await users.nodes;
|
|
2753
|
+
this.logActivity("list_users", "user", "all", {
|
|
2754
|
+
count: userList.length
|
|
2755
|
+
}, true);
|
|
2756
|
+
return userList;
|
|
2757
|
+
}
|
|
2758
|
+
async getCurrentUser() {
|
|
2759
|
+
const user = await this.client.viewer;
|
|
2760
|
+
this.logActivity("get_current_user", "user", user.id, {
|
|
2761
|
+
email: user.email,
|
|
2762
|
+
name: user.name
|
|
2763
|
+
}, true);
|
|
2764
|
+
return user;
|
|
2765
|
+
}
|
|
2766
|
+
async getUserTeams() {
|
|
2767
|
+
const viewer = await this.client.viewer;
|
|
2768
|
+
const teams = await viewer.teams();
|
|
2769
|
+
const teamList = await teams.nodes;
|
|
2770
|
+
this.logActivity("list_user_teams", "team", viewer.id, {
|
|
2771
|
+
count: teamList.length
|
|
2772
|
+
}, true);
|
|
2773
|
+
return teamList;
|
|
2774
|
+
}
|
|
2775
|
+
async getLabels(teamId) {
|
|
2776
|
+
const query = this.client.issueLabels({
|
|
2777
|
+
first: 100,
|
|
2778
|
+
filter: teamId ? {
|
|
2779
|
+
team: { id: { eq: teamId } }
|
|
2780
|
+
} : undefined
|
|
2781
|
+
});
|
|
2782
|
+
const labels = await query;
|
|
2783
|
+
const labelList = await labels.nodes;
|
|
2784
|
+
this.logActivity("list_labels", "label", "all", {
|
|
2785
|
+
count: labelList.length,
|
|
2786
|
+
teamId
|
|
2787
|
+
}, true);
|
|
2788
|
+
return labelList;
|
|
2789
|
+
}
|
|
2790
|
+
async getWorkflowStates(teamId) {
|
|
2791
|
+
const states = await this.client.workflowStates({
|
|
2792
|
+
filter: {
|
|
2793
|
+
team: { id: { eq: teamId } }
|
|
2794
|
+
}
|
|
2795
|
+
});
|
|
2796
|
+
const stateList = await states.nodes;
|
|
2797
|
+
this.logActivity("list_workflow_states", "team", teamId, {
|
|
2798
|
+
count: stateList.length
|
|
2799
|
+
}, true);
|
|
2800
|
+
return stateList;
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2651
2804
|
// src/index.ts
|
|
2652
2805
|
var linearPlugin = {
|
|
2653
|
-
name: "@elizaos/plugin-linear",
|
|
2806
|
+
name: "@elizaos/plugin-linear-ts",
|
|
2654
2807
|
description: "Plugin for integrating with Linear issue tracking system",
|
|
2655
2808
|
services: [LinearService],
|
|
2656
2809
|
actions: [
|
|
@@ -2666,17 +2819,15 @@ var linearPlugin = {
|
|
|
2666
2819
|
clearActivityAction
|
|
2667
2820
|
],
|
|
2668
2821
|
providers: [
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2822
|
+
linearIssuesProvider,
|
|
2823
|
+
linearTeamsProvider,
|
|
2824
|
+
linearProjectsProvider,
|
|
2825
|
+
linearActivityProvider
|
|
2673
2826
|
]
|
|
2674
2827
|
};
|
|
2675
2828
|
export {
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
LinearRateLimitError,
|
|
2679
|
-
LinearService,
|
|
2680
|
-
linearPlugin
|
|
2829
|
+
linearPlugin,
|
|
2830
|
+
LinearService
|
|
2681
2831
|
};
|
|
2682
|
-
|
|
2832
|
+
|
|
2833
|
+
//# debugId=AB1E47AE7B755C0964756E2164756E21
|