@elizaos/plugin-linear 1.2.10 → 1.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -12
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1344 -253
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -195,16 +195,68 @@ var _LinearService = class _LinearService extends Service {
|
|
|
195
195
|
throw new LinearAPIError(`Failed to update issue: ${errorMessage}`);
|
|
196
196
|
}
|
|
197
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");
|
|
204
|
+
}
|
|
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
|
+
}
|
|
198
212
|
async searchIssues(filters) {
|
|
199
213
|
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
|
+
};
|
|
256
|
+
}
|
|
200
257
|
const query = this.client.issues({
|
|
201
258
|
first: filters.limit || 50,
|
|
202
|
-
filter:
|
|
203
|
-
or: [
|
|
204
|
-
{ title: { containsIgnoreCase: filters.query } },
|
|
205
|
-
{ description: { containsIgnoreCase: filters.query } }
|
|
206
|
-
]
|
|
207
|
-
} : void 0
|
|
259
|
+
filter: Object.keys(filterObject).length > 0 ? filterObject : void 0
|
|
208
260
|
});
|
|
209
261
|
const issues = await query;
|
|
210
262
|
const issueList = await issues.nodes;
|
|
@@ -494,10 +546,25 @@ var createIssueAction = {
|
|
|
494
546
|
}
|
|
495
547
|
}
|
|
496
548
|
if (!issueData.teamId) {
|
|
497
|
-
const
|
|
498
|
-
if (
|
|
499
|
-
|
|
500
|
-
|
|
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
|
+
}
|
|
501
568
|
}
|
|
502
569
|
}
|
|
503
570
|
} catch (parseError) {
|
|
@@ -506,10 +573,20 @@ var createIssueAction = {
|
|
|
506
573
|
title: content.length > 100 ? content.substring(0, 100) + "..." : content,
|
|
507
574
|
description: content
|
|
508
575
|
};
|
|
576
|
+
const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
|
|
509
577
|
const teams = await linearService.getTeams();
|
|
510
|
-
if (
|
|
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) {
|
|
511
588
|
issueData.teamId = teams[0].id;
|
|
512
|
-
logger2.warn(`Using
|
|
589
|
+
logger2.warn(`Using first available team for fallback: ${teams[0].name}`);
|
|
513
590
|
}
|
|
514
591
|
}
|
|
515
592
|
}
|
|
@@ -568,11 +645,37 @@ View it at: ${issue.url}`;
|
|
|
568
645
|
};
|
|
569
646
|
|
|
570
647
|
// src/actions/getIssue.ts
|
|
571
|
-
import { logger as logger3 } from "@elizaos/core";
|
|
648
|
+
import { logger as logger3, ModelType as ModelType2 } from "@elizaos/core";
|
|
649
|
+
var getIssueTemplate = `Extract issue identification from the user's request.
|
|
650
|
+
|
|
651
|
+
User request: "{{userMessage}}"
|
|
652
|
+
|
|
653
|
+
The user might reference an issue by:
|
|
654
|
+
- Direct ID (e.g., "ENG-123", "COM2-7")
|
|
655
|
+
- Title keywords (e.g., "the login bug", "that payment issue")
|
|
656
|
+
- Assignee (e.g., "John's high priority task")
|
|
657
|
+
- Recency (e.g., "the latest bug", "most recent issue")
|
|
658
|
+
- Team context (e.g., "newest issue in ELIZA team")
|
|
659
|
+
|
|
660
|
+
Return ONLY a JSON object:
|
|
661
|
+
{
|
|
662
|
+
"directId": "Issue ID if explicitly mentioned (e.g., ENG-123)",
|
|
663
|
+
"searchBy": {
|
|
664
|
+
"title": "Keywords from issue title if mentioned",
|
|
665
|
+
"assignee": "Name/email of assignee if mentioned",
|
|
666
|
+
"priority": "Priority level if mentioned (urgent/high/normal/low or 1-4)",
|
|
667
|
+
"team": "Team name or key if mentioned",
|
|
668
|
+
"state": "Issue state if mentioned (todo/in-progress/done)",
|
|
669
|
+
"recency": "latest/newest/recent/last if mentioned",
|
|
670
|
+
"type": "bug/feature/task if mentioned"
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
Only include fields that are clearly mentioned or implied.`;
|
|
572
675
|
var getIssueAction = {
|
|
573
676
|
name: "GET_LINEAR_ISSUE",
|
|
574
677
|
description: "Get details of a specific Linear issue",
|
|
575
|
-
similes: ["get-linear-issue", "show-linear-issue", "view-linear-issue"],
|
|
678
|
+
similes: ["get-linear-issue", "show-linear-issue", "view-linear-issue", "check-linear-issue", "find-linear-issue"],
|
|
576
679
|
examples: [[
|
|
577
680
|
{
|
|
578
681
|
name: "User",
|
|
@@ -591,13 +694,27 @@ var getIssueAction = {
|
|
|
591
694
|
{
|
|
592
695
|
name: "User",
|
|
593
696
|
content: {
|
|
594
|
-
text: "What's the status of
|
|
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"
|
|
595
712
|
}
|
|
596
713
|
},
|
|
597
714
|
{
|
|
598
715
|
name: "Assistant",
|
|
599
716
|
content: {
|
|
600
|
-
text: "
|
|
717
|
+
text: "I'll find the latest high priority issue assigned to Sarah.",
|
|
601
718
|
actions: ["GET_LINEAR_ISSUE"]
|
|
602
719
|
}
|
|
603
720
|
}
|
|
@@ -618,102 +735,133 @@ var getIssueAction = {
|
|
|
618
735
|
}
|
|
619
736
|
const content = message.content.text;
|
|
620
737
|
if (!content) {
|
|
621
|
-
const
|
|
738
|
+
const errorMessage2 = "Please specify which issue you want to see.";
|
|
622
739
|
await callback?.({
|
|
623
|
-
text:
|
|
740
|
+
text: errorMessage2,
|
|
624
741
|
source: message.content.source
|
|
625
742
|
});
|
|
626
743
|
return {
|
|
627
|
-
text:
|
|
744
|
+
text: errorMessage2,
|
|
628
745
|
success: false
|
|
629
746
|
};
|
|
630
747
|
}
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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");
|
|
642
759
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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")}
|
|
830
|
+
|
|
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.";
|
|
709
858
|
await callback?.({
|
|
710
|
-
text:
|
|
859
|
+
text: errorMessage,
|
|
711
860
|
source: message.content.source
|
|
712
861
|
});
|
|
713
862
|
return {
|
|
714
|
-
text:
|
|
715
|
-
success:
|
|
716
|
-
data: issueDetails
|
|
863
|
+
text: errorMessage,
|
|
864
|
+
success: false
|
|
717
865
|
};
|
|
718
866
|
} catch (error) {
|
|
719
867
|
logger3.error("Failed to get issue:", error);
|
|
@@ -729,13 +877,103 @@ View in Linear: ${issue.url}`;
|
|
|
729
877
|
}
|
|
730
878
|
}
|
|
731
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}` : ""}
|
|
937
|
+
|
|
938
|
+
${issue.description || "No description"}
|
|
939
|
+
|
|
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
|
+
};
|
|
950
|
+
}
|
|
732
951
|
|
|
733
952
|
// src/actions/updateIssue.ts
|
|
734
|
-
import { logger as logger4 } from "@elizaos/core";
|
|
953
|
+
import { logger as logger4, ModelType as ModelType3 } from "@elizaos/core";
|
|
954
|
+
var updateIssueTemplate = `Given the user's request to update a Linear issue, extract the information needed.
|
|
955
|
+
|
|
956
|
+
User request: "{{userMessage}}"
|
|
957
|
+
|
|
958
|
+
Extract and return ONLY a JSON object (no markdown formatting, no code blocks) with the following structure:
|
|
959
|
+
{
|
|
960
|
+
"issueId": "The issue identifier (e.g., ENG-123, COM2-7)",
|
|
961
|
+
"updates": {
|
|
962
|
+
"title": "New title if changing the title",
|
|
963
|
+
"description": "New description if changing the description",
|
|
964
|
+
"priority": "Priority level if changing (1=urgent, 2=high, 3=normal, 4=low)",
|
|
965
|
+
"teamKey": "New team key if moving to another team (e.g., ENG, ELIZA, COM2)",
|
|
966
|
+
"assignee": "New assignee username or email if changing",
|
|
967
|
+
"status": "New status if changing (e.g., todo, in-progress, done, canceled)",
|
|
968
|
+
"labels": ["label1", "label2"] (if changing labels, empty array to clear)
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
Only include fields that are being updated. Return only the JSON object, no other text.`;
|
|
735
973
|
var updateIssueAction = {
|
|
736
974
|
name: "UPDATE_LINEAR_ISSUE",
|
|
737
975
|
description: "Update an existing Linear issue",
|
|
738
|
-
similes: ["update-linear-issue", "edit-linear-issue", "modify-linear-issue"],
|
|
976
|
+
similes: ["update-linear-issue", "edit-linear-issue", "modify-linear-issue", "move-linear-issue", "change-linear-issue"],
|
|
739
977
|
examples: [[
|
|
740
978
|
{
|
|
741
979
|
name: "User",
|
|
@@ -754,13 +992,27 @@ var updateIssueAction = {
|
|
|
754
992
|
{
|
|
755
993
|
name: "User",
|
|
756
994
|
content: {
|
|
757
|
-
text: "
|
|
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"]
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
], [
|
|
1006
|
+
{
|
|
1007
|
+
name: "User",
|
|
1008
|
+
content: {
|
|
1009
|
+
text: "Change the priority of BUG-456 to high and assign to john@example.com"
|
|
758
1010
|
}
|
|
759
1011
|
},
|
|
760
1012
|
{
|
|
761
1013
|
name: "Assistant",
|
|
762
1014
|
content: {
|
|
763
|
-
text: "I'll change the priority of BUG-456 to high.",
|
|
1015
|
+
text: "I'll change the priority of BUG-456 to high and assign it to john@example.com.",
|
|
764
1016
|
actions: ["UPDATE_LINEAR_ISSUE"]
|
|
765
1017
|
}
|
|
766
1018
|
}
|
|
@@ -791,9 +1043,126 @@ var updateIssueAction = {
|
|
|
791
1043
|
success: false
|
|
792
1044
|
};
|
|
793
1045
|
}
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
|
|
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
|
+
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");
|
|
1061
|
+
}
|
|
1062
|
+
if (parsed.updates?.title) {
|
|
1063
|
+
updates.title = parsed.updates.title;
|
|
1064
|
+
}
|
|
1065
|
+
if (parsed.updates?.description) {
|
|
1066
|
+
updates.description = parsed.updates.description;
|
|
1067
|
+
}
|
|
1068
|
+
if (parsed.updates?.priority) {
|
|
1069
|
+
updates.priority = Number(parsed.updates.priority);
|
|
1070
|
+
}
|
|
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
|
+
}
|
|
1082
|
+
}
|
|
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`);
|
|
1093
|
+
}
|
|
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`);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
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);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
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
|
|
1157
|
+
};
|
|
1158
|
+
const priority = priorityMap[priorityMatch[1].toLowerCase()];
|
|
1159
|
+
if (priority) {
|
|
1160
|
+
updates.priority = priority;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
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'")`;
|
|
797
1166
|
await callback?.({
|
|
798
1167
|
text: errorMessage,
|
|
799
1168
|
source: message.content.source
|
|
@@ -803,36 +1172,122 @@ var updateIssueAction = {
|
|
|
803
1172
|
success: false
|
|
804
1173
|
};
|
|
805
1174
|
}
|
|
806
|
-
const
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
if (
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
if (
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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(", ")}
|
|
1184
|
+
|
|
1185
|
+
View it at: ${updatedIssue.url}`;
|
|
1186
|
+
await callback?.({
|
|
1187
|
+
text: successMessage,
|
|
1188
|
+
source: message.content.source
|
|
1189
|
+
});
|
|
1190
|
+
return {
|
|
1191
|
+
text: `Updated issue ${updatedIssue.identifier}: ${updateSummary.join(", ")}`,
|
|
1192
|
+
success: true,
|
|
1193
|
+
data: {
|
|
1194
|
+
issueId: updatedIssue.id,
|
|
1195
|
+
identifier: updatedIssue.identifier,
|
|
1196
|
+
updates,
|
|
1197
|
+
url: updatedIssue.url
|
|
824
1198
|
}
|
|
1199
|
+
};
|
|
1200
|
+
} 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"}`;
|
|
1203
|
+
await callback?.({
|
|
1204
|
+
text: errorMessage,
|
|
1205
|
+
source: message.content.source
|
|
1206
|
+
});
|
|
1207
|
+
return {
|
|
1208
|
+
text: errorMessage,
|
|
1209
|
+
success: false
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
// 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.`;
|
|
1227
|
+
var deleteIssueAction = {
|
|
1228
|
+
name: "DELETE_LINEAR_ISSUE",
|
|
1229
|
+
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"
|
|
825
1236
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1237
|
+
},
|
|
1238
|
+
{
|
|
1239
|
+
name: "Assistant",
|
|
1240
|
+
content: {
|
|
1241
|
+
text: "I'll archive issue ENG-123 for you.",
|
|
1242
|
+
actions: ["DELETE_LINEAR_ISSUE"]
|
|
829
1243
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1244
|
+
}
|
|
1245
|
+
], [
|
|
1246
|
+
{
|
|
1247
|
+
name: "User",
|
|
1248
|
+
content: {
|
|
1249
|
+
text: "Remove COM2-7 from Linear"
|
|
833
1250
|
}
|
|
834
|
-
|
|
835
|
-
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
name: "Assistant",
|
|
1254
|
+
content: {
|
|
1255
|
+
text: "I'll archive issue COM2-7 in Linear.",
|
|
1256
|
+
actions: ["DELETE_LINEAR_ISSUE"]
|
|
1257
|
+
}
|
|
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"]
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
]],
|
|
1274
|
+
async validate(runtime, _message, _state) {
|
|
1275
|
+
try {
|
|
1276
|
+
const apiKey = runtime.getSetting("LINEAR_API_KEY");
|
|
1277
|
+
return !!apiKey;
|
|
1278
|
+
} catch {
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
},
|
|
1282
|
+
async handler(runtime, message, _state, _options, callback) {
|
|
1283
|
+
try {
|
|
1284
|
+
const linearService = runtime.getService("linear");
|
|
1285
|
+
if (!linearService) {
|
|
1286
|
+
throw new Error("Linear service not available");
|
|
1287
|
+
}
|
|
1288
|
+
const content = message.content.text;
|
|
1289
|
+
if (!content) {
|
|
1290
|
+
const errorMessage = "Please specify which issue to delete.";
|
|
836
1291
|
await callback?.({
|
|
837
1292
|
text: errorMessage,
|
|
838
1293
|
source: message.content.source
|
|
@@ -842,28 +1297,66 @@ var updateIssueAction = {
|
|
|
842
1297
|
success: false
|
|
843
1298
|
};
|
|
844
1299
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1300
|
+
let issueId;
|
|
1301
|
+
if (_options?.issueId) {
|
|
1302
|
+
issueId = _options.issueId;
|
|
1303
|
+
} else {
|
|
1304
|
+
const prompt = deleteIssueTemplate.replace("{{userMessage}}", content);
|
|
1305
|
+
const response = await runtime.useModel(ModelType4.TEXT_LARGE, {
|
|
1306
|
+
prompt
|
|
1307
|
+
});
|
|
1308
|
+
if (!response) {
|
|
1309
|
+
throw new Error("Failed to extract issue identifier");
|
|
1310
|
+
}
|
|
1311
|
+
try {
|
|
1312
|
+
const cleanedResponse = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
|
|
1313
|
+
const parsed = JSON.parse(cleanedResponse);
|
|
1314
|
+
issueId = parsed.issueId;
|
|
1315
|
+
if (!issueId) {
|
|
1316
|
+
throw new Error("Issue ID not found in parsed response");
|
|
1317
|
+
}
|
|
1318
|
+
} catch (parseError) {
|
|
1319
|
+
logger5.warn("Failed to parse LLM response, falling back to regex parsing:", parseError);
|
|
1320
|
+
const issueMatch = content.match(/(\w+-\d+)/);
|
|
1321
|
+
if (!issueMatch) {
|
|
1322
|
+
const errorMessage = "Please specify an issue ID (e.g., ENG-123) to delete.";
|
|
1323
|
+
await callback?.({
|
|
1324
|
+
text: errorMessage,
|
|
1325
|
+
source: message.content.source
|
|
1326
|
+
});
|
|
1327
|
+
return {
|
|
1328
|
+
text: errorMessage,
|
|
1329
|
+
success: false
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
issueId = issueMatch[1];
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
const issue = await linearService.getIssue(issueId);
|
|
1336
|
+
const issueTitle = issue.title;
|
|
1337
|
+
const issueIdentifier = issue.identifier;
|
|
1338
|
+
logger5.info(`Archiving issue ${issueIdentifier}: ${issueTitle}`);
|
|
1339
|
+
await linearService.deleteIssue(issueId);
|
|
1340
|
+
const successMessage = `\u2705 Successfully archived issue ${issueIdentifier}: "${issueTitle}"
|
|
848
1341
|
|
|
849
|
-
|
|
1342
|
+
The issue has been moved to the archived state and will no longer appear in active views.`;
|
|
850
1343
|
await callback?.({
|
|
851
1344
|
text: successMessage,
|
|
852
1345
|
source: message.content.source
|
|
853
1346
|
});
|
|
854
1347
|
return {
|
|
855
|
-
text: `
|
|
1348
|
+
text: `Archived issue ${issueIdentifier}: "${issueTitle}"`,
|
|
856
1349
|
success: true,
|
|
857
1350
|
data: {
|
|
858
|
-
issueId:
|
|
859
|
-
identifier:
|
|
860
|
-
|
|
861
|
-
|
|
1351
|
+
issueId: issue.id,
|
|
1352
|
+
identifier: issueIdentifier,
|
|
1353
|
+
title: issueTitle,
|
|
1354
|
+
archived: true
|
|
862
1355
|
}
|
|
863
1356
|
};
|
|
864
1357
|
} catch (error) {
|
|
865
|
-
|
|
866
|
-
const errorMessage = `\u274C Failed to
|
|
1358
|
+
logger5.error("Failed to delete issue:", error);
|
|
1359
|
+
const errorMessage = `\u274C Failed to delete issue: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
867
1360
|
await callback?.({
|
|
868
1361
|
text: errorMessage,
|
|
869
1362
|
source: message.content.source
|
|
@@ -878,30 +1371,50 @@ View it at: ${updatedIssue.url}`;
|
|
|
878
1371
|
|
|
879
1372
|
// src/actions/searchIssues.ts
|
|
880
1373
|
import {
|
|
881
|
-
ModelType as
|
|
882
|
-
logger as
|
|
1374
|
+
ModelType as ModelType5,
|
|
1375
|
+
logger as logger6
|
|
883
1376
|
} from "@elizaos/core";
|
|
884
1377
|
var searchTemplate = `Extract search criteria from the user's request for Linear issues.
|
|
885
1378
|
|
|
886
1379
|
User request: "{{userMessage}}"
|
|
887
1380
|
|
|
888
|
-
|
|
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:
|
|
889
1392
|
{
|
|
890
|
-
"query": "
|
|
891
|
-
"
|
|
892
|
-
"
|
|
893
|
-
"
|
|
894
|
-
"
|
|
895
|
-
"
|
|
896
|
-
"hasAssignee": true/false
|
|
897
|
-
"
|
|
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)
|
|
898
1411
|
}
|
|
899
1412
|
|
|
900
|
-
Only include fields that are mentioned.
|
|
1413
|
+
Only include fields that are clearly mentioned or implied. For "my" issues, set assignees to ["me"].`;
|
|
901
1414
|
var searchIssuesAction = {
|
|
902
1415
|
name: "SEARCH_LINEAR_ISSUES",
|
|
903
1416
|
description: "Search for issues in Linear with various filters",
|
|
904
|
-
similes: ["search-linear-issues", "find-linear-issues", "query-linear-issues"],
|
|
1417
|
+
similes: ["search-linear-issues", "find-linear-issues", "query-linear-issues", "list-linear-issues"],
|
|
905
1418
|
examples: [[
|
|
906
1419
|
{
|
|
907
1420
|
name: "User",
|
|
@@ -920,13 +1433,27 @@ var searchIssuesAction = {
|
|
|
920
1433
|
{
|
|
921
1434
|
name: "User",
|
|
922
1435
|
content: {
|
|
923
|
-
text: "
|
|
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"]
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
], [
|
|
1447
|
+
{
|
|
1448
|
+
name: "User",
|
|
1449
|
+
content: {
|
|
1450
|
+
text: "Show me high priority issues created this week"
|
|
924
1451
|
}
|
|
925
1452
|
},
|
|
926
1453
|
{
|
|
927
1454
|
name: "Assistant",
|
|
928
1455
|
content: {
|
|
929
|
-
text: "I'll search for high priority issues
|
|
1456
|
+
text: "I'll search for high priority issues created this week.",
|
|
930
1457
|
actions: ["SEARCH_LINEAR_ISSUES"]
|
|
931
1458
|
}
|
|
932
1459
|
}
|
|
@@ -962,7 +1489,7 @@ var searchIssuesAction = {
|
|
|
962
1489
|
filters = _options.filters;
|
|
963
1490
|
} else {
|
|
964
1491
|
const prompt = searchTemplate.replace("{{userMessage}}", content);
|
|
965
|
-
const response = await runtime.useModel(
|
|
1492
|
+
const response = await runtime.useModel(ModelType5.TEXT_LARGE, {
|
|
966
1493
|
prompt
|
|
967
1494
|
});
|
|
968
1495
|
if (!response) {
|
|
@@ -973,25 +1500,79 @@ var searchIssuesAction = {
|
|
|
973
1500
|
const parsed = JSON.parse(cleanedResponse);
|
|
974
1501
|
filters = {
|
|
975
1502
|
query: parsed.query,
|
|
976
|
-
|
|
977
|
-
assignee: parsed.assignee ? [parsed.assignee] : void 0,
|
|
978
|
-
priority: parsed.priority ? [parsed.priority] : void 0,
|
|
979
|
-
team: parsed.team,
|
|
980
|
-
label: parsed.label ? [parsed.label] : void 0,
|
|
981
|
-
limit: parsed.limit
|
|
1503
|
+
limit: parsed.limit || 10
|
|
982
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);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
if (processedAssignees.length > 0) {
|
|
1523
|
+
filters.assignee = processedAssignees;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
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
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
if (parsed.teams && parsed.teams.length > 0) {
|
|
1546
|
+
filters.team = parsed.teams[0];
|
|
1547
|
+
}
|
|
1548
|
+
if (parsed.labels && parsed.labels.length > 0) {
|
|
1549
|
+
filters.label = parsed.labels;
|
|
1550
|
+
}
|
|
1551
|
+
if (parsed.dateRange || parsed.sort) {
|
|
1552
|
+
logger6.info("Date range and sort filters noted but not yet implemented");
|
|
1553
|
+
}
|
|
983
1554
|
Object.keys(filters).forEach((key) => {
|
|
984
1555
|
if (filters[key] === void 0) {
|
|
985
1556
|
delete filters[key];
|
|
986
1557
|
}
|
|
987
1558
|
});
|
|
988
1559
|
} catch (parseError) {
|
|
989
|
-
|
|
1560
|
+
logger6.error("Failed to parse search filters:", parseError);
|
|
990
1561
|
filters = { query: content };
|
|
991
1562
|
}
|
|
992
1563
|
}
|
|
993
1564
|
}
|
|
994
|
-
filters.
|
|
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
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
filters.limit = _options?.limit || filters.limit || 10;
|
|
995
1576
|
const issues = await linearService.searchIssues(filters);
|
|
996
1577
|
if (issues.length === 0) {
|
|
997
1578
|
const noResultsMessage = "No issues found matching your search criteria.";
|
|
@@ -1011,37 +1592,47 @@ var searchIssuesAction = {
|
|
|
1011
1592
|
}
|
|
1012
1593
|
const issueList = await Promise.all(issues.map(async (issue, index) => {
|
|
1013
1594
|
const state = await issue.state;
|
|
1014
|
-
|
|
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"}`;
|
|
1015
1600
|
}));
|
|
1016
|
-
const issueText = issueList.join("\n");
|
|
1601
|
+
const issueText = issueList.join("\n\n");
|
|
1017
1602
|
const resultMessage = `\u{1F4CB} Found ${issues.length} issue${issues.length === 1 ? "" : "s"}:
|
|
1603
|
+
|
|
1018
1604
|
${issueText}`;
|
|
1019
1605
|
await callback?.({
|
|
1020
1606
|
text: resultMessage,
|
|
1021
1607
|
source: message.content.source
|
|
1022
1608
|
});
|
|
1023
1609
|
return {
|
|
1024
|
-
text: `Found ${issues.length} issue${issues.length === 1 ? "" : "s"}
|
|
1025
|
-
${issueText}`,
|
|
1610
|
+
text: `Found ${issues.length} issue${issues.length === 1 ? "" : "s"}`,
|
|
1026
1611
|
success: true,
|
|
1027
1612
|
data: {
|
|
1028
|
-
issues: issues.map((
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
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;
|
|
1617
|
+
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
|
|
1628
|
+
};
|
|
1038
1629
|
})),
|
|
1039
1630
|
filters,
|
|
1040
1631
|
count: issues.length
|
|
1041
1632
|
}
|
|
1042
1633
|
};
|
|
1043
1634
|
} catch (error) {
|
|
1044
|
-
|
|
1635
|
+
logger6.error("Failed to search issues:", error);
|
|
1045
1636
|
const errorMessage = `\u274C Failed to search issues: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1046
1637
|
await callback?.({
|
|
1047
1638
|
text: errorMessage,
|
|
@@ -1056,22 +1647,56 @@ ${issueText}`,
|
|
|
1056
1647
|
};
|
|
1057
1648
|
|
|
1058
1649
|
// src/actions/createComment.ts
|
|
1059
|
-
import { logger as
|
|
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"
|
|
1661
|
+
|
|
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)"
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
Extract the core message the user wants to convey as the comment body.`;
|
|
1060
1671
|
var createCommentAction = {
|
|
1061
1672
|
name: "CREATE_LINEAR_COMMENT",
|
|
1062
|
-
description: "
|
|
1063
|
-
similes: ["create-linear-comment", "add-linear-comment", "comment-on-linear-issue"],
|
|
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"],
|
|
1064
1675
|
examples: [[
|
|
1065
1676
|
{
|
|
1066
1677
|
name: "User",
|
|
1067
1678
|
content: {
|
|
1068
|
-
text: "Comment on ENG-123: This
|
|
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"]
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
], [
|
|
1690
|
+
{
|
|
1691
|
+
name: "User",
|
|
1692
|
+
content: {
|
|
1693
|
+
text: "Tell the login bug that we need more information from QA"
|
|
1069
1694
|
}
|
|
1070
1695
|
},
|
|
1071
1696
|
{
|
|
1072
1697
|
name: "Assistant",
|
|
1073
1698
|
content: {
|
|
1074
|
-
text: "I'll add that comment to issue
|
|
1699
|
+
text: "I'll add that comment to the login bug issue.",
|
|
1075
1700
|
actions: ["CREATE_LINEAR_COMMENT"]
|
|
1076
1701
|
}
|
|
1077
1702
|
}
|
|
@@ -1079,13 +1704,13 @@ var createCommentAction = {
|
|
|
1079
1704
|
{
|
|
1080
1705
|
name: "User",
|
|
1081
1706
|
content: {
|
|
1082
|
-
text: "
|
|
1707
|
+
text: "Reply to COM2-7: Thanks for the update, I'll look into it"
|
|
1083
1708
|
}
|
|
1084
1709
|
},
|
|
1085
1710
|
{
|
|
1086
1711
|
name: "Assistant",
|
|
1087
1712
|
content: {
|
|
1088
|
-
text: "I'll
|
|
1713
|
+
text: "I'll add your reply to issue COM2-7.",
|
|
1089
1714
|
actions: ["CREATE_LINEAR_COMMENT"]
|
|
1090
1715
|
}
|
|
1091
1716
|
}
|
|
@@ -1106,7 +1731,7 @@ var createCommentAction = {
|
|
|
1106
1731
|
}
|
|
1107
1732
|
const content = message.content.text;
|
|
1108
1733
|
if (!content) {
|
|
1109
|
-
const errorMessage = "Please provide a message with the issue
|
|
1734
|
+
const errorMessage = "Please provide a message with the issue and comment content.";
|
|
1110
1735
|
await callback?.({
|
|
1111
1736
|
text: errorMessage,
|
|
1112
1737
|
source: message.content.source
|
|
@@ -1116,9 +1741,108 @@ var createCommentAction = {
|
|
|
1116
1741
|
success: false
|
|
1117
1742
|
};
|
|
1118
1743
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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);
|
|
1751
|
+
const response = await runtime.useModel(ModelType6.TEXT_LARGE, {
|
|
1752
|
+
prompt
|
|
1753
|
+
});
|
|
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 {
|
|
1763
|
+
try {
|
|
1764
|
+
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
|
+
};
|
|
1818
|
+
}
|
|
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
|
+
}
|
|
1825
|
+
} 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
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
issueId = issueMatch[1];
|
|
1840
|
+
commentBody = issueMatch[2].trim();
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
if (!commentBody || commentBody.length === 0) {
|
|
1845
|
+
const errorMessage = "Please provide the comment content.";
|
|
1122
1846
|
await callback?.({
|
|
1123
1847
|
text: errorMessage,
|
|
1124
1848
|
source: message.content.source
|
|
@@ -1128,27 +1852,29 @@ var createCommentAction = {
|
|
|
1128
1852
|
success: false
|
|
1129
1853
|
};
|
|
1130
1854
|
}
|
|
1131
|
-
const
|
|
1132
|
-
const issue = await linearService.getIssue(issueIdentifier);
|
|
1855
|
+
const issue = await linearService.getIssue(issueId);
|
|
1133
1856
|
const comment = await linearService.createComment({
|
|
1134
1857
|
issueId: issue.id,
|
|
1135
|
-
body: commentBody
|
|
1858
|
+
body: commentBody
|
|
1136
1859
|
});
|
|
1137
|
-
const successMessage = `\u2705 Comment added to issue ${
|
|
1860
|
+
const successMessage = `\u2705 Comment added to issue ${issue.identifier}: "${commentBody}"`;
|
|
1138
1861
|
await callback?.({
|
|
1139
1862
|
text: successMessage,
|
|
1140
1863
|
source: message.content.source
|
|
1141
1864
|
});
|
|
1142
1865
|
return {
|
|
1143
|
-
text: `
|
|
1866
|
+
text: `Added comment to issue ${issue.identifier}`,
|
|
1144
1867
|
success: true,
|
|
1145
1868
|
data: {
|
|
1146
1869
|
commentId: comment.id,
|
|
1147
|
-
issueId: issue.id
|
|
1870
|
+
issueId: issue.id,
|
|
1871
|
+
issueIdentifier: issue.identifier,
|
|
1872
|
+
commentBody,
|
|
1873
|
+
createdAt: comment.createdAt
|
|
1148
1874
|
}
|
|
1149
1875
|
};
|
|
1150
1876
|
} catch (error) {
|
|
1151
|
-
|
|
1877
|
+
logger7.error("Failed to create comment:", error);
|
|
1152
1878
|
const errorMessage = `\u274C Failed to create comment: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1153
1879
|
await callback?.({
|
|
1154
1880
|
text: errorMessage,
|
|
@@ -1163,11 +1889,34 @@ var createCommentAction = {
|
|
|
1163
1889
|
};
|
|
1164
1890
|
|
|
1165
1891
|
// src/actions/listTeams.ts
|
|
1166
|
-
import { logger as
|
|
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.`;
|
|
1167
1916
|
var listTeamsAction = {
|
|
1168
1917
|
name: "LIST_LINEAR_TEAMS",
|
|
1169
|
-
description: "List
|
|
1170
|
-
similes: ["list-linear-teams", "show-linear-teams", "get-linear-teams"],
|
|
1918
|
+
description: "List teams in Linear with optional filters",
|
|
1919
|
+
similes: ["list-linear-teams", "show-linear-teams", "get-linear-teams", "view-linear-teams"],
|
|
1171
1920
|
examples: [[
|
|
1172
1921
|
{
|
|
1173
1922
|
name: "User",
|
|
@@ -1186,13 +1935,27 @@ var listTeamsAction = {
|
|
|
1186
1935
|
{
|
|
1187
1936
|
name: "User",
|
|
1188
1937
|
content: {
|
|
1189
|
-
text: "
|
|
1938
|
+
text: "Which engineering teams do we have?"
|
|
1190
1939
|
}
|
|
1191
1940
|
},
|
|
1192
1941
|
{
|
|
1193
1942
|
name: "Assistant",
|
|
1194
1943
|
content: {
|
|
1195
|
-
text: "Let me
|
|
1944
|
+
text: "Let me find the engineering teams for you.",
|
|
1945
|
+
actions: ["LIST_LINEAR_TEAMS"]
|
|
1946
|
+
}
|
|
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.",
|
|
1196
1959
|
actions: ["LIST_LINEAR_TEAMS"]
|
|
1197
1960
|
}
|
|
1198
1961
|
}
|
|
@@ -1211,9 +1974,51 @@ var listTeamsAction = {
|
|
|
1211
1974
|
if (!linearService) {
|
|
1212
1975
|
throw new Error("Linear service not available");
|
|
1213
1976
|
}
|
|
1214
|
-
const
|
|
1977
|
+
const content = message.content.text || "";
|
|
1978
|
+
let nameFilter;
|
|
1979
|
+
let specificTeam;
|
|
1980
|
+
let myTeams = false;
|
|
1981
|
+
let includeDetails = false;
|
|
1982
|
+
if (content) {
|
|
1983
|
+
const prompt = listTeamsTemplate.replace("{{userMessage}}", content);
|
|
1984
|
+
const response = await runtime.useModel(ModelType7.TEXT_LARGE, {
|
|
1985
|
+
prompt
|
|
1986
|
+
});
|
|
1987
|
+
if (response) {
|
|
1988
|
+
try {
|
|
1989
|
+
const parsed = JSON.parse(response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim());
|
|
1990
|
+
nameFilter = parsed.nameFilter;
|
|
1991
|
+
specificTeam = parsed.specificTeam;
|
|
1992
|
+
myTeams = parsed.myTeams === true;
|
|
1993
|
+
includeDetails = parsed.includeDetails === true;
|
|
1994
|
+
} catch (parseError) {
|
|
1995
|
+
logger8.warn("Failed to parse team filters:", parseError);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
let teams = await linearService.getTeams();
|
|
2000
|
+
if (specificTeam) {
|
|
2001
|
+
teams = teams.filter(
|
|
2002
|
+
(team) => team.key.toLowerCase() === specificTeam.toLowerCase() || team.name.toLowerCase() === specificTeam.toLowerCase()
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
if (nameFilter && !specificTeam) {
|
|
2006
|
+
const keywords = nameFilter.toLowerCase().split(/\s+/);
|
|
2007
|
+
teams = teams.filter((team) => {
|
|
2008
|
+
const teamText = `${team.name} ${team.description || ""}`.toLowerCase();
|
|
2009
|
+
return keywords.some((keyword) => teamText.includes(keyword));
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
if (myTeams) {
|
|
2013
|
+
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");
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
1215
2020
|
if (teams.length === 0) {
|
|
1216
|
-
const noTeamsMessage = "No teams found in Linear.";
|
|
2021
|
+
const noTeamsMessage = specificTeam ? `No team found matching "${specificTeam}".` : nameFilter ? `No teams found matching "${nameFilter}".` : "No teams found in Linear.";
|
|
1217
2022
|
await callback?.({
|
|
1218
2023
|
text: noTeamsMessage,
|
|
1219
2024
|
source: message.content.source
|
|
@@ -1226,31 +2031,68 @@ var listTeamsAction = {
|
|
|
1226
2031
|
}
|
|
1227
2032
|
};
|
|
1228
2033
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
2034
|
+
let teamsWithDetails = teams;
|
|
2035
|
+
if (includeDetails || specificTeam) {
|
|
2036
|
+
teamsWithDetails = await Promise.all(teams.map(async (team) => {
|
|
2037
|
+
const membersQuery = await team.members();
|
|
2038
|
+
const members = await membersQuery.nodes;
|
|
2039
|
+
const projectsQuery = await team.projects();
|
|
2040
|
+
const projects = await projectsQuery.nodes;
|
|
2041
|
+
return {
|
|
2042
|
+
...team,
|
|
2043
|
+
memberCount: members.length,
|
|
2044
|
+
projectCount: projects.length,
|
|
2045
|
+
membersList: specificTeam ? members.slice(0, 5) : []
|
|
2046
|
+
// Include member details for specific team
|
|
2047
|
+
};
|
|
2048
|
+
}));
|
|
2049
|
+
}
|
|
2050
|
+
const teamList = teamsWithDetails.map((team, index) => {
|
|
2051
|
+
let info = `${index + 1}. ${team.name} (${team.key})`;
|
|
2052
|
+
if (team.description) {
|
|
2053
|
+
info += `
|
|
2054
|
+
${team.description}`;
|
|
2055
|
+
}
|
|
2056
|
+
if (includeDetails || specificTeam) {
|
|
2057
|
+
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(", ");
|
|
2061
|
+
info += `
|
|
2062
|
+
Team members: ${memberNames}${team.memberCount > 5 ? " ..." : ""}`;
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
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"}:`;
|
|
2068
|
+
const resultMessage = `${headerText}
|
|
2069
|
+
|
|
1233
2070
|
${teamList}`;
|
|
1234
2071
|
await callback?.({
|
|
1235
2072
|
text: resultMessage,
|
|
1236
2073
|
source: message.content.source
|
|
1237
2074
|
});
|
|
1238
2075
|
return {
|
|
1239
|
-
text: `Found ${teams.length} team${teams.length === 1 ? "" : "s"}
|
|
1240
|
-
${teamList}`,
|
|
2076
|
+
text: `Found ${teams.length} team${teams.length === 1 ? "" : "s"}`,
|
|
1241
2077
|
success: true,
|
|
1242
2078
|
data: {
|
|
1243
|
-
teams:
|
|
2079
|
+
teams: teamsWithDetails.map((t) => ({
|
|
1244
2080
|
id: t.id,
|
|
1245
2081
|
name: t.name,
|
|
1246
2082
|
key: t.key,
|
|
1247
|
-
description: t.description
|
|
2083
|
+
description: t.description,
|
|
2084
|
+
memberCount: t.memberCount,
|
|
2085
|
+
projectCount: t.projectCount
|
|
1248
2086
|
})),
|
|
1249
|
-
count: teams.length
|
|
2087
|
+
count: teams.length,
|
|
2088
|
+
filters: {
|
|
2089
|
+
name: nameFilter,
|
|
2090
|
+
specific: specificTeam
|
|
2091
|
+
}
|
|
1250
2092
|
}
|
|
1251
2093
|
};
|
|
1252
2094
|
} catch (error) {
|
|
1253
|
-
|
|
2095
|
+
logger8.error("Failed to list teams:", error);
|
|
1254
2096
|
const errorMessage = `\u274C Failed to list teams: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1255
2097
|
await callback?.({
|
|
1256
2098
|
text: errorMessage,
|
|
@@ -1265,11 +2107,40 @@ ${teamList}`,
|
|
|
1265
2107
|
};
|
|
1266
2108
|
|
|
1267
2109
|
// src/actions/listProjects.ts
|
|
1268
|
-
import { logger as
|
|
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.`;
|
|
1269
2140
|
var listProjectsAction = {
|
|
1270
2141
|
name: "LIST_LINEAR_PROJECTS",
|
|
1271
|
-
description: "List
|
|
1272
|
-
similes: ["list-linear-projects", "show-linear-projects", "get-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"],
|
|
1273
2144
|
examples: [[
|
|
1274
2145
|
{
|
|
1275
2146
|
name: "User",
|
|
@@ -1288,13 +2159,27 @@ var listProjectsAction = {
|
|
|
1288
2159
|
{
|
|
1289
2160
|
name: "User",
|
|
1290
2161
|
content: {
|
|
1291
|
-
text: "What projects do we have?"
|
|
2162
|
+
text: "What active projects do we have?"
|
|
1292
2163
|
}
|
|
1293
2164
|
},
|
|
1294
2165
|
{
|
|
1295
2166
|
name: "Assistant",
|
|
1296
2167
|
content: {
|
|
1297
|
-
text: "Let me show you all the
|
|
2168
|
+
text: "Let me show you all the active projects.",
|
|
2169
|
+
actions: ["LIST_LINEAR_PROJECTS"]
|
|
2170
|
+
}
|
|
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.",
|
|
1298
2183
|
actions: ["LIST_LINEAR_PROJECTS"]
|
|
1299
2184
|
}
|
|
1300
2185
|
}
|
|
@@ -1313,9 +2198,79 @@ var listProjectsAction = {
|
|
|
1313
2198
|
if (!linearService) {
|
|
1314
2199
|
throw new Error("Linear service not available");
|
|
1315
2200
|
}
|
|
1316
|
-
const
|
|
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);
|
|
2207
|
+
const response = await runtime.useModel(ModelType8.TEXT_LARGE, {
|
|
2208
|
+
prompt
|
|
2209
|
+
});
|
|
2210
|
+
if (response) {
|
|
2211
|
+
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})`);
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
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");
|
|
2227
|
+
}
|
|
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})`);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
showAll = content.toLowerCase().includes("all") && content.toLowerCase().includes("project");
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
if (!teamId && !showAll) {
|
|
2246
|
+
const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
|
|
2247
|
+
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})`);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
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
|
+
}
|
|
1317
2272
|
if (projects.length === 0) {
|
|
1318
|
-
const noProjectsMessage = "No projects found in Linear.";
|
|
2273
|
+
const noProjectsMessage = teamId ? "No projects found for the specified team." : "No projects found in Linear.";
|
|
1319
2274
|
await callback?.({
|
|
1320
2275
|
text: noProjectsMessage,
|
|
1321
2276
|
source: message.content.source
|
|
@@ -1332,25 +2287,37 @@ var listProjectsAction = {
|
|
|
1332
2287
|
projects.map(async (project) => {
|
|
1333
2288
|
const teamsQuery = await project.teams();
|
|
1334
2289
|
const teams = await teamsQuery.nodes;
|
|
2290
|
+
const lead = await project.lead;
|
|
1335
2291
|
return {
|
|
1336
2292
|
...project,
|
|
1337
|
-
teamsList: teams
|
|
2293
|
+
teamsList: teams,
|
|
2294
|
+
leadUser: lead
|
|
1338
2295
|
};
|
|
1339
2296
|
})
|
|
1340
2297
|
);
|
|
1341
2298
|
const projectList = projectsWithDetails.map((project, index) => {
|
|
1342
2299
|
const teamNames = project.teamsList.map((t) => t.name).join(", ") || "No teams";
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
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}
|
|
2313
|
+
|
|
1346
2314
|
${projectList}`;
|
|
1347
2315
|
await callback?.({
|
|
1348
2316
|
text: resultMessage,
|
|
1349
2317
|
source: message.content.source
|
|
1350
2318
|
});
|
|
1351
2319
|
return {
|
|
1352
|
-
text: `Found ${projects.length} project${projects.length === 1 ? "" : "s"}
|
|
1353
|
-
${projectList}`,
|
|
2320
|
+
text: `Found ${projects.length} project${projects.length === 1 ? "" : "s"}`,
|
|
1354
2321
|
success: true,
|
|
1355
2322
|
data: {
|
|
1356
2323
|
projects: projectsWithDetails.map((p) => ({
|
|
@@ -1363,16 +2330,25 @@ ${projectList}`,
|
|
|
1363
2330
|
name: t.name,
|
|
1364
2331
|
key: t.key
|
|
1365
2332
|
})),
|
|
2333
|
+
lead: p.leadUser ? {
|
|
2334
|
+
id: p.leadUser.id,
|
|
2335
|
+
name: p.leadUser.name,
|
|
2336
|
+
email: p.leadUser.email
|
|
2337
|
+
} : null,
|
|
1366
2338
|
state: p.state,
|
|
1367
2339
|
progress: p.progress,
|
|
1368
2340
|
startDate: p.startDate,
|
|
1369
2341
|
targetDate: p.targetDate
|
|
1370
2342
|
})),
|
|
1371
|
-
count: projects.length
|
|
2343
|
+
count: projects.length,
|
|
2344
|
+
filters: {
|
|
2345
|
+
team: teamId,
|
|
2346
|
+
state: stateFilter
|
|
2347
|
+
}
|
|
1372
2348
|
}
|
|
1373
2349
|
};
|
|
1374
2350
|
} catch (error) {
|
|
1375
|
-
|
|
2351
|
+
logger9.error("Failed to list projects:", error);
|
|
1376
2352
|
const errorMessage = `\u274C Failed to list projects: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1377
2353
|
await callback?.({
|
|
1378
2354
|
text: errorMessage,
|
|
@@ -1387,11 +2363,39 @@ ${projectList}`,
|
|
|
1387
2363
|
};
|
|
1388
2364
|
|
|
1389
2365
|
// src/actions/getActivity.ts
|
|
1390
|
-
import { logger as
|
|
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.`;
|
|
1391
2395
|
var getActivityAction = {
|
|
1392
2396
|
name: "GET_LINEAR_ACTIVITY",
|
|
1393
|
-
description: "Get recent Linear activity",
|
|
1394
|
-
similes: ["get-linear-activity", "show-linear-activity", "view-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"],
|
|
1395
2399
|
examples: [[
|
|
1396
2400
|
{
|
|
1397
2401
|
name: "User",
|
|
@@ -1402,7 +2406,7 @@ var getActivityAction = {
|
|
|
1402
2406
|
{
|
|
1403
2407
|
name: "Assistant",
|
|
1404
2408
|
content: {
|
|
1405
|
-
text: "I'll
|
|
2409
|
+
text: "I'll show you the recent Linear activity.",
|
|
1406
2410
|
actions: ["GET_LINEAR_ACTIVITY"]
|
|
1407
2411
|
}
|
|
1408
2412
|
}
|
|
@@ -1410,13 +2414,27 @@ var getActivityAction = {
|
|
|
1410
2414
|
{
|
|
1411
2415
|
name: "User",
|
|
1412
2416
|
content: {
|
|
1413
|
-
text: "What
|
|
2417
|
+
text: "What happened in Linear today?"
|
|
1414
2418
|
}
|
|
1415
2419
|
},
|
|
1416
2420
|
{
|
|
1417
2421
|
name: "Assistant",
|
|
1418
2422
|
content: {
|
|
1419
|
-
text: "Let me
|
|
2423
|
+
text: "Let me check today's Linear activity for you.",
|
|
2424
|
+
actions: ["GET_LINEAR_ACTIVITY"]
|
|
2425
|
+
}
|
|
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.",
|
|
1420
2438
|
actions: ["GET_LINEAR_ACTIVITY"]
|
|
1421
2439
|
}
|
|
1422
2440
|
}
|
|
@@ -1435,9 +2453,74 @@ var getActivityAction = {
|
|
|
1435
2453
|
if (!linearService) {
|
|
1436
2454
|
throw new Error("Linear service not available");
|
|
1437
2455
|
}
|
|
1438
|
-
const
|
|
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
|
|
2463
|
+
});
|
|
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;
|
|
2506
|
+
}
|
|
2507
|
+
if (parsed.successFilter && parsed.successFilter !== "all") {
|
|
2508
|
+
filters.success = parsed.successFilter === "success";
|
|
2509
|
+
}
|
|
2510
|
+
limit = parsed.limit || 10;
|
|
2511
|
+
} catch (parseError) {
|
|
2512
|
+
logger10.warn("Failed to parse activity filters:", parseError);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
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);
|
|
1439
2522
|
if (activity.length === 0) {
|
|
1440
|
-
const noActivityMessage = "No recent Linear activity found.";
|
|
2523
|
+
const noActivityMessage = filters.fromDate ? `No Linear activity found for the specified filters.` : "No recent Linear activity found.";
|
|
1441
2524
|
await callback?.({
|
|
1442
2525
|
text: noActivityMessage,
|
|
1443
2526
|
source: message.content.source
|
|
@@ -1450,28 +2533,35 @@ var getActivityAction = {
|
|
|
1450
2533
|
}
|
|
1451
2534
|
};
|
|
1452
2535
|
}
|
|
1453
|
-
const activityText = activity.
|
|
1454
|
-
const
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
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}
|
|
2547
|
+
|
|
1458
2548
|
${activityText}`;
|
|
1459
2549
|
await callback?.({
|
|
1460
2550
|
text: resultMessage,
|
|
1461
2551
|
source: message.content.source
|
|
1462
2552
|
});
|
|
1463
2553
|
return {
|
|
1464
|
-
text: `
|
|
1465
|
-
${activityText}`,
|
|
2554
|
+
text: `Found ${activity.length} activity item${activity.length === 1 ? "" : "s"}`,
|
|
1466
2555
|
success: true,
|
|
1467
2556
|
data: {
|
|
1468
|
-
activity
|
|
2557
|
+
activity,
|
|
2558
|
+
filters,
|
|
1469
2559
|
count: activity.length
|
|
1470
2560
|
}
|
|
1471
2561
|
};
|
|
1472
2562
|
} catch (error) {
|
|
1473
|
-
|
|
1474
|
-
const errorMessage = `\u274C Failed to get
|
|
2563
|
+
logger10.error("Failed to get activity:", error);
|
|
2564
|
+
const errorMessage = `\u274C Failed to get activity: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1475
2565
|
await callback?.({
|
|
1476
2566
|
text: errorMessage,
|
|
1477
2567
|
source: message.content.source
|
|
@@ -1485,7 +2575,7 @@ ${activityText}`,
|
|
|
1485
2575
|
};
|
|
1486
2576
|
|
|
1487
2577
|
// src/actions/clearActivity.ts
|
|
1488
|
-
import { logger as
|
|
2578
|
+
import { logger as logger11 } from "@elizaos/core";
|
|
1489
2579
|
var clearActivityAction = {
|
|
1490
2580
|
name: "CLEAR_LINEAR_ACTIVITY",
|
|
1491
2581
|
description: "Clear the Linear activity log",
|
|
@@ -1544,7 +2634,7 @@ var clearActivityAction = {
|
|
|
1544
2634
|
success: true
|
|
1545
2635
|
};
|
|
1546
2636
|
} catch (error) {
|
|
1547
|
-
|
|
2637
|
+
logger11.error("Failed to clear Linear activity:", error);
|
|
1548
2638
|
const errorMessage = `\u274C Failed to clear Linear activity: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1549
2639
|
await callback?.({
|
|
1550
2640
|
text: errorMessage,
|
|
@@ -1567,6 +2657,7 @@ var linearPlugin = {
|
|
|
1567
2657
|
createIssueAction,
|
|
1568
2658
|
getIssueAction,
|
|
1569
2659
|
updateIssueAction,
|
|
2660
|
+
deleteIssueAction,
|
|
1570
2661
|
searchIssuesAction,
|
|
1571
2662
|
createCommentAction,
|
|
1572
2663
|
listTeamsAction,
|