@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/dist/index.js CHANGED
@@ -1,420 +1,105 @@
1
- // src/services/linear.ts
2
- import { logger, Service } from "@elizaos/core";
3
- import { LinearClient } from "@linear/sdk";
4
-
5
- // src/types/index.ts
6
- var LinearAPIError = class extends Error {
7
- constructor(message, status, response) {
8
- super(message);
9
- this.status = status;
10
- this.response = response;
11
- this.name = "LinearAPIError";
12
- }
13
- };
14
- var LinearAuthenticationError = class extends LinearAPIError {
15
- constructor(message) {
16
- super(message, 401);
17
- this.name = "LinearAuthenticationError";
18
- }
19
- };
20
- var LinearRateLimitError = class extends LinearAPIError {
21
- constructor(message, resetTime) {
22
- super(message, 429);
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
- this.logActivity("update_issue", "issue", issueId, updates, true);
191
- return issue;
192
- } catch (error) {
193
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
194
- this.logActivity("update_issue", "issue", issueId, updates, false, errorMessage);
195
- throw new LinearAPIError(`Failed to update issue: ${errorMessage}`);
196
- }
197
- }
198
- async deleteIssue(issueId) {
199
- try {
200
- const archivePayload = await this.client.archiveIssue(issueId);
201
- const success = await archivePayload.success;
202
- if (!success) {
203
- throw new Error("Failed to archive issue");
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
- this.logActivity("delete_issue", "issue", issueId, { action: "archived" }, true);
206
- } catch (error) {
207
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
208
- this.logActivity("delete_issue", "issue", issueId, { action: "archive_failed" }, false, errorMessage);
209
- throw new LinearAPIError(`Failed to archive issue: ${errorMessage}`);
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
- let filterObject = {};
215
- if (filters.query) {
216
- filterObject.or = [
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
- const query = this.client.issues({
258
- first: filters.limit || 50,
259
- filter: Object.keys(filterObject).length > 0 ? filterObject : void 0
260
- });
261
- const issues = await query;
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
- const labels = await query;
378
- const labelList = await labels.nodes;
379
- this.logActivity("list_labels", "label", "all", {
380
- count: labelList.length,
381
- teamId
382
- }, true);
383
- return labelList;
57
+ return {
58
+ text: successMessage,
59
+ success: true
60
+ };
384
61
  } catch (error) {
385
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
386
- this.logActivity("list_labels", "label", "all", { teamId }, false, errorMessage);
387
- throw new LinearAPIError(`Failed to fetch labels: ${errorMessage}`);
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
- const stateList = await states.nodes;
399
- this.logActivity("list_workflow_states", "team", teamId, {
400
- count: stateList.length
401
- }, true);
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/createIssue.ts
76
+ // src/actions/createComment.ts
414
77
  import {
415
- ModelType,
416
- logger as logger2
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 createIssueAction = {
434
- name: "CREATE_LINEAR_ISSUE",
435
- description: "Create a new issue in Linear",
436
- similes: ["create-linear-issue", "new-linear-issue", "add-linear-issue"],
437
- examples: [[
438
- {
439
- name: "User",
440
- content: {
441
- text: "Create a new issue: Fix login button not working on mobile devices"
442
- }
443
- },
444
- {
445
- name: "Assistant",
446
- content: {
447
- text: "I'll create that issue for you in Linear.",
448
- actions: ["CREATE_LINEAR_ISSUE"]
449
- }
450
- }
451
- ], [
452
- {
453
- name: "User",
454
- content: {
455
- text: "Create a bug report for the ENG team: API returns 500 error when updating user profile"
456
- }
457
- },
458
- {
459
- name: "Assistant",
460
- content: {
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
- async handler(runtime, message, _state, _options, callback) {
475
- try {
476
- const linearService = runtime.getService("linear");
477
- if (!linearService) {
478
- throw new Error("Linear service not available");
479
- }
480
- const content = message.content.text;
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
- // src/actions/getIssue.ts
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 getIssueAction = {
676
- name: "GET_LINEAR_ISSUE",
677
- description: "Get details of a specific Linear issue",
678
- similes: ["get-linear-issue", "show-linear-issue", "view-linear-issue", "check-linear-issue", "find-linear-issue"],
679
- examples: [[
680
- {
681
- name: "User",
682
- content: {
683
- text: "Show me issue ENG-123"
684
- }
685
- },
686
- {
687
- name: "Assistant",
688
- content: {
689
- text: "I'll get the details for issue ENG-123.",
690
- actions: ["GET_LINEAR_ISSUE"]
691
- }
692
- }
693
- ], [
694
- {
695
- name: "User",
696
- content: {
697
- text: "What's the status of the login bug?"
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
- async handler(runtime, message, _state, _options, callback) {
731
- try {
732
- const linearService = runtime.getService("linear");
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
- Please specify which one you want to see by its ID.`;
832
- await callback?.({
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
- ${issue.description || "No description"}
213
+ User request: "{{userMessage}}"
939
214
 
940
- View in Linear: ${issue.url}`;
941
- await callback?.({
942
- text: issueMessage,
943
- source: message.content.source
944
- });
945
- return {
946
- text: `Retrieved issue ${issue.identifier}: ${issue.title}`,
947
- success: true,
948
- data: { issue: issueDetails }
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
- // src/actions/updateIssue.ts
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
- var updateIssueAction = {
974
- name: "UPDATE_LINEAR_ISSUE",
975
- description: "Update an existing Linear issue",
976
- similes: ["update-linear-issue", "edit-linear-issue", "modify-linear-issue", "move-linear-issue", "change-linear-issue"],
977
- examples: [[
978
- {
979
- name: "User",
980
- content: {
981
- text: 'Update issue ENG-123 title to "Fix login button on all devices"'
982
- }
983
- },
984
- {
985
- name: "Assistant",
986
- content: {
987
- text: "I'll update the title of issue ENG-123 for you.",
988
- actions: ["UPDATE_LINEAR_ISSUE"]
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
- name: "User",
994
- content: {
995
- text: "Move issue COM2-7 to the ELIZA team"
996
- }
997
- },
998
- {
999
- name: "Assistant",
1000
- content: {
1001
- text: "I'll move issue COM2-7 to the ELIZA team.",
1002
- actions: ["UPDATE_LINEAR_ISSUE"]
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
- name: "User",
1008
- content: {
1009
- text: "Change the priority of BUG-456 to high and assign to john@example.com"
1010
- }
1011
- },
1012
- {
1013
- name: "Assistant",
1014
- content: {
1015
- text: "I'll change the priority of BUG-456 to high and assign it to john@example.com.",
1016
- actions: ["UPDATE_LINEAR_ISSUE"]
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
- try {
1022
- const apiKey = runtime.getSetting("LINEAR_API_KEY");
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 update instructions for the issue.";
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 updates = {};
1055
- try {
1056
- const cleanedResponse = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
1057
- const parsed = JSON.parse(cleanedResponse);
1058
- issueId = parsed.issueId;
1059
- if (!issueId) {
1060
- throw new Error("Issue ID not found in parsed response");
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
- if (parsed.updates?.title) {
1063
- updates.title = parsed.updates.title;
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
- if (parsed.updates?.description) {
1066
- updates.description = parsed.updates.description;
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
- if (parsed.updates?.priority) {
1069
- updates.priority = Number(parsed.updates.priority);
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
- if (parsed.updates?.teamKey) {
1072
- const teams = await linearService.getTeams();
1073
- const team = teams.find(
1074
- (t) => t.key.toLowerCase() === parsed.updates.teamKey.toLowerCase()
1075
- );
1076
- if (team) {
1077
- updates.teamId = team.id;
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
- if (parsed.updates?.assignee) {
1084
- const cleanAssignee = parsed.updates.assignee.replace(/^@/, "");
1085
- const users = await linearService.getUsers();
1086
- const user = users.find(
1087
- (u) => u.email === cleanAssignee || u.name.toLowerCase().includes(cleanAssignee.toLowerCase())
1088
- );
1089
- if (user) {
1090
- updates.assigneeId = user.id;
1091
- } else {
1092
- logger4.warn(`User ${cleanAssignee} not found`);
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
- if (parsed.updates?.status) {
1096
- const issue = await linearService.getIssue(issueId);
1097
- const issueTeam = await issue.team;
1098
- const teamId = updates.teamId || issueTeam?.id;
1099
- if (!teamId) {
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
- if (parsed.updates?.labels && Array.isArray(parsed.updates.labels)) {
1115
- const teamId = updates.teamId;
1116
- const labels = await linearService.getLabels(teamId);
1117
- const labelIds = [];
1118
- for (const labelName of parsed.updates.labels) {
1119
- if (labelName) {
1120
- const label = labels.find(
1121
- (l) => l.name.toLowerCase() === labelName.toLowerCase()
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
- updates.labelIds = labelIds;
1129
- }
1130
- } catch (parseError) {
1131
- logger4.warn("Failed to parse LLM response, falling back to regex parsing:", parseError);
1132
- const issueMatch = content.match(/(\w+-\d+)/);
1133
- if (!issueMatch) {
1134
- const errorMessage = "Please specify an issue ID (e.g., ENG-123) to update.";
1135
- await callback?.({
1136
- text: errorMessage,
1137
- source: message.content.source
1138
- });
1139
- return {
1140
- text: errorMessage,
1141
- success: false
1142
- };
1143
- }
1144
- issueId = issueMatch[1];
1145
- const titleMatch = content.match(/title to ["'](.+?)["']/i);
1146
- if (titleMatch) {
1147
- updates.title = titleMatch[1];
1148
- }
1149
- const priorityMatch = content.match(/priority (?:to |as )?(\w+)/i);
1150
- if (priorityMatch) {
1151
- const priorityMap = {
1152
- "urgent": 1,
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 priority = priorityMap[priorityMatch[1].toLowerCase()];
1159
- if (priority) {
1160
- updates.priority = priority;
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 (Object.keys(updates).length === 0) {
1165
- const errorMessage = `No valid updates found. Please specify what to update (e.g., "Update issue ENG-123 title to 'New Title'")`;
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
- const updatedIssue = await linearService.updateIssue(issueId, updates);
1176
- const updateSummary = [];
1177
- if (updates.title) updateSummary.push(`title: "${updates.title}"`);
1178
- if (updates.priority) updateSummary.push(`priority: ${["", "urgent", "high", "normal", "low"][updates.priority]}`);
1179
- if (updates.teamId) updateSummary.push(`moved to team`);
1180
- if (updates.assigneeId) updateSummary.push(`assigned to user`);
1181
- if (updates.stateId) updateSummary.push(`status changed`);
1182
- if (updates.labelIds) updateSummary.push(`labels updated`);
1183
- const successMessage = `\u2705 Updated issue ${updatedIssue.identifier}: ${updateSummary.join(", ")}
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: ${updatedIssue.url}`;
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: `Updated issue ${updatedIssue.identifier}: ${updateSummary.join(", ")}`,
705
+ text: `Created issue: ${issue.title} (${issue.identifier})`,
1192
706
  success: true,
1193
707
  data: {
1194
- issueId: updatedIssue.id,
1195
- identifier: updatedIssue.identifier,
1196
- updates,
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
- logger4.error("Failed to update issue:", error);
1202
- const errorMessage = `\u274C Failed to update issue: ${error instanceof Error ? error.message : "Unknown error"}`;
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 { logger as logger5, ModelType as ModelType4 } from "@elizaos/core";
1217
- var deleteIssueTemplate = `Given the user's request to delete/archive a Linear issue, extract the issue identifier.
1218
-
1219
- User request: "{{userMessage}}"
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: ["delete-linear-issue", "archive-linear-issue", "remove-linear-issue", "close-linear-issue"],
1231
- examples: [[
1232
- {
1233
- name: "User",
1234
- content: {
1235
- text: "Delete issue ENG-123"
1236
- }
1237
- },
1238
- {
1239
- name: "Assistant",
1240
- content: {
1241
- text: "I'll archive issue ENG-123 for you.",
1242
- actions: ["DELETE_LINEAR_ISSUE"]
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
- name: "User",
1248
- content: {
1249
- text: "Remove COM2-7 from Linear"
1250
- }
1251
- },
1252
- {
1253
- name: "Assistant",
1254
- content: {
1255
- text: "I'll archive issue COM2-7 in Linear.",
1256
- actions: ["DELETE_LINEAR_ISSUE"]
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
- name: "User",
1262
- content: {
1263
- text: "Archive the bug report BUG-456"
1264
- }
1265
- },
1266
- {
1267
- name: "Assistant",
1268
- content: {
1269
- text: "I'll archive issue BUG-456 for you.",
1270
- actions: ["DELETE_LINEAR_ISSUE"]
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
- try {
1276
- const apiKey = runtime.getSetting("LINEAR_API_KEY");
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
- if (_options?.issueId) {
1302
- issueId = _options.issueId;
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(ModelType4.TEXT_LARGE, {
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
- logger5.warn("Failed to parse LLM response, falling back to regex parsing:", parseError);
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
- logger5.info(`Archiving issue ${issueIdentifier}: ${issueTitle}`);
850
+ logger4.info(`Archiving issue ${issueIdentifier}: ${issueTitle}`);
1339
851
  await linearService.deleteIssue(issueId);
1340
- const successMessage = `\u2705 Successfully archived issue ${issueIdentifier}: "${issueTitle}"
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
- logger5.error("Failed to delete issue:", error);
1359
- const errorMessage = `\u274C Failed to delete issue: ${error instanceof Error ? error.message : "Unknown error"}`;
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/searchIssues.ts
884
+ // src/actions/getActivity.ts
1373
885
  import {
1374
- ModelType as ModelType5,
1375
- logger as logger6
886
+ logger as logger5,
887
+ ModelType as ModelType4
1376
888
  } from "@elizaos/core";
1377
- var searchTemplate = `Extract search criteria from the user's request for Linear issues.
1378
-
1379
- User request: "{{userMessage}}"
1380
-
1381
- The user might express searches in various ways:
1382
- - "Show me what John is working on" \u2192 assignee filter
1383
- - "Any blockers for the next release?" \u2192 priority/label filters
1384
- - "Issues created this week" \u2192 date range filter
1385
- - "My high priority bugs" \u2192 assignee (current user) + priority + label
1386
- - "Unassigned tasks in the backend team" \u2192 no assignee + team filter
1387
- - "What did Sarah close yesterday?" \u2192 assignee + state + date
1388
- - "Bugs that are almost done" \u2192 label + state filter
1389
- - "Show me the oldest open issues" \u2192 state + sort order
1390
-
1391
- Extract and return ONLY a JSON object:
1392
- {
1393
- "query": "General search text for title/description",
1394
- "states": ["state names like In Progress, Done, Todo, Backlog"],
1395
- "assignees": ["assignee names or emails, or 'me' for current user"],
1396
- "priorities": ["urgent/high/normal/low or 1/2/3/4"],
1397
- "teams": ["team names or keys"],
1398
- "labels": ["label names"],
1399
- "hasAssignee": true/false (true = has assignee, false = unassigned),
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
- name: "User",
1435
- content: {
1436
- text: "What is John working on?"
1437
- }
1438
- },
1439
- {
1440
- name: "Assistant",
1441
- content: {
1442
- text: "I'll find the issues assigned to John.",
1443
- actions: ["SEARCH_LINEAR_ISSUES"]
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
- name: "User",
1449
- content: {
1450
- text: "Show me high priority issues created this week"
1451
- }
1452
- },
1453
- {
1454
- name: "Assistant",
1455
- content: {
1456
- text: "I'll search for high priority issues created this week.",
1457
- actions: ["SEARCH_LINEAR_ISSUES"]
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
- try {
1463
- const apiKey = runtime.getSetting("LINEAR_API_KEY");
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
- if (!content) {
1477
- const errorMessage = "Please provide search criteria for issues.";
1478
- await callback?.({
1479
- text: errorMessage,
1480
- source: message.content.source
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 (!response) {
1496
- filters = { query: content };
1497
- } else {
963
+ if (response) {
1498
964
  try {
1499
- const cleanedResponse = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
1500
- const parsed = JSON.parse(cleanedResponse);
1501
- filters = {
1502
- query: parsed.query,
1503
- limit: parsed.limit || 10
1504
- };
1505
- if (parsed.states && parsed.states.length > 0) {
1506
- filters.state = parsed.states;
1507
- }
1508
- if (parsed.assignees && parsed.assignees.length > 0) {
1509
- const processedAssignees = [];
1510
- for (const assignee of parsed.assignees) {
1511
- if (assignee.toLowerCase() === "me") {
1512
- try {
1513
- const currentUser = await linearService.getCurrentUser();
1514
- processedAssignees.push(currentUser.email);
1515
- } catch {
1516
- logger6.warn('Could not resolve "me" to current user');
1517
- }
1518
- } else {
1519
- processedAssignees.push(assignee);
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 (processedAssignees.length > 0) {
1523
- filters.assignee = processedAssignees;
993
+ if (fromDate) {
994
+ filters.fromDate = fromDate.toISOString();
1524
995
  }
1525
996
  }
1526
- if (parsed.hasAssignee === false) {
1527
- filters.query = (filters.query ? filters.query + " " : "") + "unassigned";
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.teams && parsed.teams.length > 0) {
1546
- filters.team = parsed.teams[0];
1000
+ if (parsed.resourceTypes && parsed.resourceTypes.length > 0) {
1001
+ filters.resource_type = parsed.resourceTypes[0];
1547
1002
  }
1548
- if (parsed.labels && parsed.labels.length > 0) {
1549
- filters.label = parsed.labels;
1003
+ if (parsed.resourceId) {
1004
+ filters.resource_id = parsed.resourceId;
1550
1005
  }
1551
- if (parsed.dateRange || parsed.sort) {
1552
- logger6.info("Date range and sort filters noted but not yet implemented");
1006
+ if (parsed.successFilter && parsed.successFilter !== "all") {
1007
+ filters.success = parsed.successFilter === "success";
1553
1008
  }
1554
- Object.keys(filters).forEach((key) => {
1555
- if (filters[key] === void 0) {
1556
- delete filters[key];
1557
- }
1558
- });
1009
+ limit = parsed.limit || 10;
1559
1010
  } catch (parseError) {
1560
- logger6.error("Failed to parse search filters:", parseError);
1561
- filters = { query: content };
1011
+ logger5.warn("Failed to parse activity filters:", parseError);
1562
1012
  }
1563
1013
  }
1564
1014
  }
1565
- if (!filters.team) {
1566
- const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
1567
- if (defaultTeamKey) {
1568
- const searchingAllIssues = content.toLowerCase().includes("all") && (content.toLowerCase().includes("issue") || content.toLowerCase().includes("bug") || content.toLowerCase().includes("task"));
1569
- if (!searchingAllIssues) {
1570
- filters.team = defaultTeamKey;
1571
- logger6.info(`Applying default team filter: ${defaultTeamKey}`);
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
- filters.limit = _options?.limit || filters.limit || 10;
1576
- const issues = await linearService.searchIssues(filters);
1577
- if (issues.length === 0) {
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: noResultsMessage,
1028
+ text: noActivityMessage,
1581
1029
  source: message.content.source
1582
1030
  });
1583
1031
  return {
1584
- text: noResultsMessage,
1032
+ text: noActivityMessage,
1585
1033
  success: true,
1586
1034
  data: {
1587
- issues: [],
1588
- filters,
1589
- count: 0
1035
+ activity: []
1590
1036
  }
1591
1037
  };
1592
1038
  }
1593
- const issueList = await Promise.all(issues.map(async (issue, index) => {
1594
- const state = await issue.state;
1595
- const assignee = await issue.assignee;
1596
- const priorityLabels = ["", "Urgent", "High", "Normal", "Low"];
1597
- const priority = priorityLabels[issue.priority || 0] || "No priority";
1598
- return `${index + 1}. ${issue.identifier}: ${issue.title}
1599
- Status: ${state?.name || "No state"} | Priority: ${priority} | Assignee: ${assignee?.name || "Unassigned"}`;
1600
- }));
1601
- const issueText = issueList.join("\n\n");
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
- ${issueText}`;
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 ${issues.length} issue${issues.length === 1 ? "" : "s"}`,
1059
+ text: `Found ${activity.length} activity item${activity.length === 1 ? "" : "s"}`,
1611
1060
  success: true,
1612
1061
  data: {
1613
- issues: await Promise.all(issues.map(async (issue) => {
1614
- const state = await issue.state;
1615
- const assignee = await issue.assignee;
1616
- const team = await issue.team;
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
- id: issue.id,
1619
- identifier: issue.identifier,
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
- filters,
1631
- count: issues.length
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 search issues:", error);
1636
- const errorMessage = `\u274C Failed to search issues: ${error instanceof Error ? error.message : "Unknown error"}`;
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
- // src/actions/createComment.ts
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
- Return ONLY a JSON object:
1663
- {
1664
- "issueId": "Direct issue ID if explicitly mentioned (e.g., ENG-123)",
1665
- "issueDescription": "Description/keywords of the issue if no ID provided",
1666
- "commentBody": "The actual comment content to add",
1667
- "commentType": "note/reply/update/question/feedback (inferred from context)"
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
- Extract the core message the user wants to convey as the comment body.`;
1671
- var createCommentAction = {
1672
- name: "CREATE_LINEAR_COMMENT",
1673
- description: "Add a comment to a Linear issue",
1674
- similes: ["create-linear-comment", "add-linear-comment", "comment-on-linear-issue", "reply-to-linear-issue"],
1675
- examples: [[
1676
- {
1677
- name: "User",
1678
- content: {
1679
- text: "Comment on ENG-123: This looks good to me"
1680
- }
1681
- },
1682
- {
1683
- name: "Assistant",
1684
- content: {
1685
- text: "I'll add your comment to issue ENG-123.",
1686
- actions: ["CREATE_LINEAR_COMMENT"]
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
- name: "User",
1692
- content: {
1693
- text: "Tell the login bug that we need more information from QA"
1694
- }
1695
- },
1696
- {
1697
- name: "Assistant",
1698
- content: {
1699
- text: "I'll add that comment to the login bug issue.",
1700
- actions: ["CREATE_LINEAR_COMMENT"]
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
- name: "User",
1706
- content: {
1707
- text: "Reply to COM2-7: Thanks for the update, I'll look into it"
1708
- }
1709
- },
1710
- {
1711
- name: "Assistant",
1712
- content: {
1713
- text: "I'll add your reply to issue COM2-7.",
1714
- actions: ["CREATE_LINEAR_COMMENT"]
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
- try {
1720
- const apiKey = runtime.getSetting("LINEAR_API_KEY");
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
- if (!content) {
1734
- const errorMessage = "Please provide a message with the issue and comment content.";
1735
- await callback?.({
1736
- text: errorMessage,
1737
- source: message.content.source
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 (!response) {
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.issueId) {
1766
- issueId = parsed.issueId;
1767
- commentBody = parsed.commentBody;
1768
- } else if (parsed.issueDescription) {
1769
- const filters = {
1770
- query: parsed.issueDescription,
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 LLM response, falling back to regex:", parseError);
1827
- const issueMatch = content.match(/(?:comment on|add.*comment.*to|reply to|tell)\s+(\w+-\d+):?\s*(.*)/i);
1828
- if (!issueMatch) {
1829
- const errorMessage = 'Please specify the issue ID and comment content. Example: "Comment on ENG-123: This looks good"';
1830
- await callback?.({
1831
- text: errorMessage,
1832
- source: message.content.source
1833
- });
1834
- return {
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
- issueId = issueMatch[1];
1840
- commentBody = issueMatch[2].trim();
1493
+ showAll = content.toLowerCase().includes("all") && content.toLowerCase().includes("project");
1841
1494
  }
1842
1495
  }
1843
1496
  }
1844
- if (!commentBody || commentBody.length === 0) {
1845
- const errorMessage = "Please provide the comment content.";
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: errorMessage,
1525
+ text: noProjectsMessage,
1848
1526
  source: message.content.source
1849
1527
  });
1850
1528
  return {
1851
- text: errorMessage,
1852
- success: false
1529
+ text: noProjectsMessage,
1530
+ success: true,
1531
+ data: {
1532
+ projects: []
1533
+ }
1853
1534
  };
1854
1535
  }
1855
- const issue = await linearService.getIssue(issueId);
1856
- const comment = await linearService.createComment({
1857
- issueId: issue.id,
1858
- body: commentBody
1859
- });
1860
- const successMessage = `\u2705 Comment added to issue ${issue.identifier}: "${commentBody}"`;
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: successMessage,
1568
+ text: resultMessage,
1863
1569
  source: message.content.source
1864
1570
  });
1865
1571
  return {
1866
- text: `Added comment to issue ${issue.identifier}`,
1572
+ text: `Found ${projects.length} project${projects.length === 1 ? "" : "s"}`,
1867
1573
  success: true,
1868
1574
  data: {
1869
- commentId: comment.id,
1870
- issueId: issue.id,
1871
- issueIdentifier: issue.identifier,
1872
- commentBody,
1873
- createdAt: comment.createdAt
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 create comment:", error);
1878
- const errorMessage = `\u274C Failed to create comment: ${error instanceof Error ? error.message : "Unknown error"}`;
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 { logger as logger8, ModelType as ModelType7 } from "@elizaos/core";
1893
- var listTeamsTemplate = `Extract team filter criteria from the user's request.
1894
-
1895
- User request: "{{userMessage}}"
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
- name: "User",
1923
- content: {
1924
- text: "Show me all teams"
1925
- }
1926
- },
1927
- {
1928
- name: "Assistant",
1929
- content: {
1930
- text: "I'll list all the teams in Linear for you.",
1931
- actions: ["LIST_LINEAR_TEAMS"]
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
- name: "User",
1937
- content: {
1938
- text: "Which engineering teams do we have?"
1939
- }
1940
- },
1941
- {
1942
- name: "Assistant",
1943
- content: {
1944
- text: "Let me find the engineering teams for you.",
1945
- actions: ["LIST_LINEAR_TEAMS"]
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
- name: "User",
1951
- content: {
1952
- text: "Show me the teams I'm part of"
1953
- }
1954
- },
1955
- {
1956
- name: "Assistant",
1957
- content: {
1958
- text: "I'll show you the teams you're a member of.",
1959
- actions: ["LIST_LINEAR_TEAMS"]
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
- try {
1965
- const apiKey = runtime.getSetting("LINEAR_API_KEY");
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 currentUser = await linearService.getCurrentUser();
2015
- logger8.info("Team membership filtering not yet implemented");
2016
- } catch {
2017
- logger8.warn("Could not get current user for team filtering");
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
- // Include member details for specific team
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
- if (specificTeam && team.membersList.length > 0) {
2060
- const memberNames = team.membersList.map((m) => m.name).join(", ");
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("\n\n");
2067
- const headerText = specificTeam && teams.length === 1 ? `\u{1F4CB} Team Details:` : nameFilter ? `\u{1F4CB} Found ${teams.length} team${teams.length === 1 ? "" : "s"} matching "${nameFilter}":` : `\u{1F4CB} Found ${teams.length} team${teams.length === 1 ? "" : "s"}:`;
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 = `\u274C Failed to list teams: ${error instanceof Error ? error.message : "Unknown error"}`;
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/listProjects.ts
2110
- import { logger as logger9, ModelType as ModelType8 } from "@elizaos/core";
2111
- var listProjectsTemplate = `Extract project filter criteria from the user's request.
2112
-
2113
- User request: "{{userMessage}}"
2114
-
2115
- The user might ask for projects in various ways:
2116
- - "Show me all projects" \u2192 list all projects
2117
- - "Active projects" \u2192 filter by state (active/planned/completed)
2118
- - "Projects due this quarter" \u2192 filter by target date
2119
- - "Which projects is Sarah managing?" \u2192 filter by lead/owner
2120
- - "Projects with high priority issues" \u2192 filter by contained issue priority
2121
- - "Projects for the engineering team" \u2192 filter by team
2122
- - "Completed projects" \u2192 filter by state
2123
- - "Projects starting next month" \u2192 filter by start date
2124
-
2125
- Return ONLY a JSON object:
2126
- {
2127
- "teamFilter": "Team name or key if mentioned",
2128
- "stateFilter": "active/planned/completed/all",
2129
- "dateFilter": {
2130
- "type": "due/starting",
2131
- "period": "this-week/this-month/this-quarter/next-month/next-quarter",
2132
- "from": "ISO date if specific",
2133
- "to": "ISO date if specific"
2134
- },
2135
- "leadFilter": "Project lead name if mentioned",
2136
- "showAll": true/false (true if user explicitly asks for "all")
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
- name: "User",
2161
- content: {
2162
- text: "What active projects do we have?"
2163
- }
2164
- },
2165
- {
2166
- name: "Assistant",
2167
- content: {
2168
- text: "Let me show you all the active projects.",
2169
- actions: ["LIST_LINEAR_PROJECTS"]
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
- name: "User",
2175
- content: {
2176
- text: "Show me projects for the engineering team"
2177
- }
2178
- },
2179
- {
2180
- name: "Assistant",
2181
- content: {
2182
- text: "I'll find the projects for the engineering team.",
2183
- actions: ["LIST_LINEAR_PROJECTS"]
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
- try {
2189
- const apiKey = runtime.getSetting("LINEAR_API_KEY");
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
- let teamId;
2203
- let showAll = false;
2204
- let stateFilter;
2205
- if (content) {
2206
- const prompt = listProjectsTemplate.replace("{{userMessage}}", content);
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 parsed = JSON.parse(response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim());
2213
- if (parsed.teamFilter) {
2214
- const teams = await linearService.getTeams();
2215
- const team = teams.find(
2216
- (t) => t.key.toLowerCase() === parsed.teamFilter.toLowerCase() || t.name.toLowerCase() === parsed.teamFilter.toLowerCase()
2217
- );
2218
- if (team) {
2219
- teamId = team.id;
2220
- logger9.info(`Filtering projects by team: ${team.name} (${team.key})`);
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
- showAll = parsed.showAll === true;
2224
- stateFilter = parsed.stateFilter;
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
- } catch (parseError) {
2229
- logger9.warn("Failed to parse project filters, using basic parsing:", parseError);
2230
- const teamMatch = content.match(/(?:for|in|of)\s+(?:the\s+)?(\w+)\s+team/i);
2231
- if (teamMatch) {
2232
- const teams = await linearService.getTeams();
2233
- const team = teams.find(
2234
- (t) => t.key.toLowerCase() === teamMatch[1].toLowerCase() || t.name.toLowerCase() === teamMatch[1].toLowerCase()
2235
- );
2236
- if (team) {
2237
- teamId = team.id;
2238
- logger9.info(`Filtering projects by team: ${team.name} (${team.key})`);
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
- showAll = content.toLowerCase().includes("all") && content.toLowerCase().includes("project");
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 (!teamId && !showAll) {
1975
+ if (!filters.team) {
2246
1976
  const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
2247
1977
  if (defaultTeamKey) {
2248
- const teams = await linearService.getTeams();
2249
- const defaultTeam = teams.find(
2250
- (t) => t.key.toLowerCase() === defaultTeamKey.toLowerCase()
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
- let projects = await linearService.getProjects(teamId);
2259
- if (stateFilter && stateFilter !== "all") {
2260
- projects = projects.filter((project) => {
2261
- const state = project.state?.toLowerCase() || "";
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: noProjectsMessage,
1990
+ text: noResultsMessage,
2276
1991
  source: message.content.source
2277
1992
  });
2278
1993
  return {
2279
- text: noProjectsMessage,
1994
+ text: noResultsMessage,
2280
1995
  success: true,
2281
1996
  data: {
2282
- projects: []
1997
+ issues: [],
1998
+ filters: filters ? { ...filters } : undefined,
1999
+ count: 0
2283
2000
  }
2284
2001
  };
2285
2002
  }
2286
- const projectsWithDetails = await Promise.all(
2287
- projects.map(async (project) => {
2288
- const teamsQuery = await project.teams();
2289
- const teams = await teamsQuery.nodes;
2290
- const lead = await project.lead;
2291
- return {
2292
- ...project,
2293
- teamsList: teams,
2294
- leadUser: lead
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
- ${projectList}`;
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 ${projects.length} project${projects.length === 1 ? "" : "s"}`,
2022
+ text: `Found ${issues.length} issue${issues.length === 1 ? "" : "s"}`,
2321
2023
  success: true,
2322
2024
  data: {
2323
- projects: projectsWithDetails.map((p) => ({
2324
- id: p.id,
2325
- name: p.name,
2326
- description: p.description,
2327
- url: p.url,
2328
- teams: p.teamsList.map((t) => ({
2329
- id: t.id,
2330
- name: t.name,
2331
- key: t.key
2332
- })),
2333
- lead: p.leadUser ? {
2334
- id: p.leadUser.id,
2335
- name: p.leadUser.name,
2336
- email: p.leadUser.email
2337
- } : null,
2338
- state: p.state,
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
- count: projects.length,
2344
- filters: {
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 list projects:", error);
2352
- const errorMessage = `\u274C Failed to list projects: ${error instanceof Error ? error.message : "Unknown error"}`;
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/getActivity.ts
2366
- import { logger as logger10, ModelType as ModelType9 } from "@elizaos/core";
2367
- var getActivityTemplate = `Extract activity filter criteria from the user's request.
2368
-
2369
- User request: "{{userMessage}}"
2370
-
2371
- The user might ask for activity in various ways:
2372
- - "Show me today's activity" \u2192 time range filter
2373
- - "What issues were created?" \u2192 action type filter
2374
- - "What did John do yesterday?" \u2192 user filter + time range
2375
- - "Activity on ENG-123" \u2192 resource filter
2376
- - "Recent comment activity" \u2192 action type + recency
2377
- - "Failed operations this week" \u2192 success filter + time range
2378
-
2379
- Return ONLY a JSON object:
2380
- {
2381
- "timeRange": {
2382
- "period": "today/yesterday/this-week/last-week/this-month",
2383
- "from": "ISO datetime if specific",
2384
- "to": "ISO datetime if specific"
2385
- },
2386
- "actionTypes": ["create_issue/update_issue/delete_issue/create_comment/search_issues/etc"],
2387
- "resourceTypes": ["issue/project/comment/team"],
2388
- "resourceId": "Specific resource ID if mentioned (e.g., ENG-123)",
2389
- "user": "User name or 'me' for current user",
2390
- "successFilter": "success/failed/all",
2391
- "limit": number (default 10)
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
- name: "User",
2416
- content: {
2417
- text: "What happened in Linear today?"
2418
- }
2419
- },
2420
- {
2421
- name: "Assistant",
2422
- content: {
2423
- text: "Let me check today's Linear activity for you.",
2424
- actions: ["GET_LINEAR_ACTIVITY"]
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
- name: "User",
2430
- content: {
2431
- text: "Show me what issues John created this week"
2432
- }
2433
- },
2434
- {
2435
- name: "Assistant",
2436
- content: {
2437
- text: "I'll find the issues John created this week.",
2438
- actions: ["GET_LINEAR_ACTIVITY"]
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
- try {
2444
- const apiKey = runtime.getSetting("LINEAR_API_KEY");
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
- let filters = {};
2458
- let limit = 10;
2459
- if (content) {
2460
- const prompt = getActivityTemplate.replace("{{userMessage}}", content);
2461
- const response = await runtime.useModel(ModelType9.TEXT_LARGE, {
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
- if (response) {
2465
- try {
2466
- const parsed = JSON.parse(response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim());
2467
- if (parsed.timeRange) {
2468
- const now = /* @__PURE__ */ new Date();
2469
- let fromDate;
2470
- if (parsed.timeRange.from) {
2471
- fromDate = new Date(parsed.timeRange.from);
2472
- } else if (parsed.timeRange.period) {
2473
- switch (parsed.timeRange.period) {
2474
- case "today":
2475
- fromDate = new Date(now.setHours(0, 0, 0, 0));
2476
- break;
2477
- case "yesterday":
2478
- fromDate = new Date(now.setDate(now.getDate() - 1));
2479
- fromDate.setHours(0, 0, 0, 0);
2480
- break;
2481
- case "this-week":
2482
- fromDate = new Date(now.setDate(now.getDate() - now.getDay()));
2483
- fromDate.setHours(0, 0, 0, 0);
2484
- break;
2485
- case "last-week":
2486
- fromDate = new Date(now.setDate(now.getDate() - now.getDay() - 7));
2487
- fromDate.setHours(0, 0, 0, 0);
2488
- break;
2489
- case "this-month":
2490
- fromDate = new Date(now.getFullYear(), now.getMonth(), 1);
2491
- break;
2492
- }
2493
- }
2494
- if (fromDate) {
2495
- filters.fromDate = fromDate.toISOString();
2496
- }
2497
- }
2498
- if (parsed.actionTypes && parsed.actionTypes.length > 0) {
2499
- filters.action = parsed.actionTypes[0];
2500
- }
2501
- if (parsed.resourceTypes && parsed.resourceTypes.length > 0) {
2502
- filters.resource_type = parsed.resourceTypes[0];
2503
- }
2504
- if (parsed.resourceId) {
2505
- filters.resource_id = parsed.resourceId;
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
- if (parsed.successFilter && parsed.successFilter !== "all") {
2508
- filters.success = parsed.successFilter === "success";
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
- limit = parsed.limit || 10;
2511
- } catch (parseError) {
2512
- logger10.warn("Failed to parse activity filters:", parseError);
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
- let activity = linearService.getActivityLog(limit * 2, filters);
2517
- if (filters.fromDate) {
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: noActivityMessage,
2258
+ text: errorMessage,
2526
2259
  source: message.content.source
2527
2260
  });
2528
2261
  return {
2529
- text: noActivityMessage,
2530
- success: true,
2531
- data: {
2532
- activity: []
2533
- }
2262
+ text: errorMessage,
2263
+ success: false
2534
2264
  };
2535
2265
  }
2536
- const activityText = activity.map((item, index) => {
2537
- const time = new Date(item.timestamp).toLocaleString();
2538
- const status = item.success ? "\u2705" : "\u274C";
2539
- const details = Object.entries(item.details).filter(([key]) => key !== "filters").map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join(", ");
2540
- return `${index + 1}. ${status} ${item.action} on ${item.resource_type} ${item.resource_id}
2541
- Time: ${time}
2542
- ${details ? `Details: ${details}` : ""}${item.error ? `
2543
- Error: ${item.error}` : ""}`;
2544
- }).join("\n\n");
2545
- const headerText = filters.fromDate ? `\u{1F4CA} Linear activity ${content}:` : "\u{1F4CA} Recent Linear activity:";
2546
- const resultMessage = `${headerText}
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
- ${activityText}`;
2282
+ View it at: ${updatedIssue.url}`;
2549
2283
  await callback?.({
2550
- text: resultMessage,
2284
+ text: successMessage,
2551
2285
  source: message.content.source
2552
2286
  });
2553
2287
  return {
2554
- text: `Found ${activity.length} activity item${activity.length === 1 ? "" : "s"}`,
2288
+ text: `Updated issue ${updatedIssue.identifier}: ${updateSummary.join(", ")}`,
2555
2289
  success: true,
2556
2290
  data: {
2557
- activity,
2558
- filters,
2559
- count: activity.length
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 get activity:", error);
2564
- const errorMessage = `\u274C Failed to get activity: ${error instanceof Error ? error.message : "Unknown error"}`;
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/actions/clearActivity.ts
2578
- import { logger as logger11 } from "@elizaos/core";
2579
- var clearActivityAction = {
2580
- name: "CLEAR_LINEAR_ACTIVITY",
2581
- description: "Clear the Linear activity log",
2582
- similes: ["clear-linear-activity", "reset-linear-activity", "delete-linear-activity"],
2583
- examples: [[
2584
- {
2585
- name: "User",
2586
- content: {
2587
- text: "Clear the Linear activity log"
2588
- }
2589
- },
2590
- {
2591
- name: "Assistant",
2592
- content: {
2593
- text: "I'll clear the Linear activity log for you.",
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
- name: "User",
2600
- content: {
2601
- text: "Reset Linear activity"
2602
- }
2603
- },
2604
- {
2605
- name: "Assistant",
2606
- content: {
2607
- text: "I'll reset the Linear activity log now.",
2608
- actions: ["CLEAR_LINEAR_ACTIVITY"]
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
- async validate(runtime, _message, _state) {
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 apiKey = runtime.getSetting("LINEAR_API_KEY");
2615
- return !!apiKey;
2616
- } catch {
2617
- return false;
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
- async handler(runtime, message, _state, _options, callback) {
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
- throw new Error("Linear service not available");
2456
+ return {
2457
+ text: "Linear service is not available"
2458
+ };
2625
2459
  }
2626
- await linearService.clearActivityLog();
2627
- const successMessage = "\u2705 Linear activity log has been cleared.";
2628
- await callback?.({
2629
- text: successMessage,
2630
- source: message.content.source
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: successMessage,
2634
- success: true
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 (error) {
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: errorMessage,
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
- // linearIssuesProvider,
2670
- // linearTeamsProvider,
2671
- // linearProjectsProvider,
2672
- // linearActivityProvider,
2822
+ linearIssuesProvider,
2823
+ linearTeamsProvider,
2824
+ linearProjectsProvider,
2825
+ linearActivityProvider
2673
2826
  ]
2674
2827
  };
2675
2828
  export {
2676
- LinearAPIError,
2677
- LinearAuthenticationError,
2678
- LinearRateLimitError,
2679
- LinearService,
2680
- linearPlugin
2829
+ linearPlugin,
2830
+ LinearService
2681
2831
  };
2682
- //# sourceMappingURL=index.js.map
2832
+
2833
+ //# debugId=AB1E47AE7B755C0964756E2164756E21