@elizaos/plugin-linear 1.2.11 → 1.2.13
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 +70 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1154 -205
- package/dist/index.js.map +1 -1
- package/package.json +24 -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;
|
|
@@ -593,11 +645,37 @@ View it at: ${issue.url}`;
|
|
|
593
645
|
};
|
|
594
646
|
|
|
595
647
|
// src/actions/getIssue.ts
|
|
596
|
-
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.`;
|
|
597
675
|
var getIssueAction = {
|
|
598
676
|
name: "GET_LINEAR_ISSUE",
|
|
599
677
|
description: "Get details of a specific Linear issue",
|
|
600
|
-
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"],
|
|
601
679
|
examples: [[
|
|
602
680
|
{
|
|
603
681
|
name: "User",
|
|
@@ -616,13 +694,27 @@ var getIssueAction = {
|
|
|
616
694
|
{
|
|
617
695
|
name: "User",
|
|
618
696
|
content: {
|
|
619
|
-
text: "What's the status of
|
|
697
|
+
text: "What's the status of the login bug?"
|
|
620
698
|
}
|
|
621
699
|
},
|
|
622
700
|
{
|
|
623
701
|
name: "Assistant",
|
|
624
702
|
content: {
|
|
625
|
-
text: "Let me
|
|
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.",
|
|
626
718
|
actions: ["GET_LINEAR_ISSUE"]
|
|
627
719
|
}
|
|
628
720
|
}
|
|
@@ -643,102 +735,133 @@ var getIssueAction = {
|
|
|
643
735
|
}
|
|
644
736
|
const content = message.content.text;
|
|
645
737
|
if (!content) {
|
|
646
|
-
const
|
|
738
|
+
const errorMessage2 = "Please specify which issue you want to see.";
|
|
647
739
|
await callback?.({
|
|
648
|
-
text:
|
|
740
|
+
text: errorMessage2,
|
|
649
741
|
source: message.content.source
|
|
650
742
|
});
|
|
651
743
|
return {
|
|
652
|
-
text:
|
|
744
|
+
text: errorMessage2,
|
|
653
745
|
success: false
|
|
654
746
|
};
|
|
655
747
|
}
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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");
|
|
667
759
|
}
|
|
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
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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.";
|
|
734
858
|
await callback?.({
|
|
735
|
-
text:
|
|
859
|
+
text: errorMessage,
|
|
736
860
|
source: message.content.source
|
|
737
861
|
});
|
|
738
862
|
return {
|
|
739
|
-
text:
|
|
740
|
-
success:
|
|
741
|
-
data: issueDetails
|
|
863
|
+
text: errorMessage,
|
|
864
|
+
success: false
|
|
742
865
|
};
|
|
743
866
|
} catch (error) {
|
|
744
867
|
logger3.error("Failed to get issue:", error);
|
|
@@ -754,9 +877,80 @@ View in Linear: ${issue.url}`;
|
|
|
754
877
|
}
|
|
755
878
|
}
|
|
756
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
|
+
}
|
|
757
951
|
|
|
758
952
|
// src/actions/updateIssue.ts
|
|
759
|
-
import { logger as logger4, ModelType as
|
|
953
|
+
import { logger as logger4, ModelType as ModelType3 } from "@elizaos/core";
|
|
760
954
|
var updateIssueTemplate = `Given the user's request to update a Linear issue, extract the information needed.
|
|
761
955
|
|
|
762
956
|
User request: "{{userMessage}}"
|
|
@@ -850,7 +1044,7 @@ var updateIssueAction = {
|
|
|
850
1044
|
};
|
|
851
1045
|
}
|
|
852
1046
|
const prompt = updateIssueTemplate.replace("{{userMessage}}", content);
|
|
853
|
-
const response = await runtime.useModel(
|
|
1047
|
+
const response = await runtime.useModel(ModelType3.TEXT_LARGE, {
|
|
854
1048
|
prompt
|
|
855
1049
|
});
|
|
856
1050
|
if (!response) {
|
|
@@ -1018,32 +1212,209 @@ View it at: ${updatedIssue.url}`;
|
|
|
1018
1212
|
}
|
|
1019
1213
|
};
|
|
1020
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"
|
|
1236
|
+
}
|
|
1237
|
+
},
|
|
1238
|
+
{
|
|
1239
|
+
name: "Assistant",
|
|
1240
|
+
content: {
|
|
1241
|
+
text: "I'll archive issue ENG-123 for you.",
|
|
1242
|
+
actions: ["DELETE_LINEAR_ISSUE"]
|
|
1243
|
+
}
|
|
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"]
|
|
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.";
|
|
1291
|
+
await callback?.({
|
|
1292
|
+
text: errorMessage,
|
|
1293
|
+
source: message.content.source
|
|
1294
|
+
});
|
|
1295
|
+
return {
|
|
1296
|
+
text: errorMessage,
|
|
1297
|
+
success: false
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
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}"
|
|
1341
|
+
|
|
1342
|
+
The issue has been moved to the archived state and will no longer appear in active views.`;
|
|
1343
|
+
await callback?.({
|
|
1344
|
+
text: successMessage,
|
|
1345
|
+
source: message.content.source
|
|
1346
|
+
});
|
|
1347
|
+
return {
|
|
1348
|
+
text: `Archived issue ${issueIdentifier}: "${issueTitle}"`,
|
|
1349
|
+
success: true,
|
|
1350
|
+
data: {
|
|
1351
|
+
issueId: issue.id,
|
|
1352
|
+
identifier: issueIdentifier,
|
|
1353
|
+
title: issueTitle,
|
|
1354
|
+
archived: true
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
} 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"}`;
|
|
1360
|
+
await callback?.({
|
|
1361
|
+
text: errorMessage,
|
|
1362
|
+
source: message.content.source
|
|
1363
|
+
});
|
|
1364
|
+
return {
|
|
1365
|
+
text: errorMessage,
|
|
1366
|
+
success: false
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
};
|
|
1371
|
+
|
|
1021
1372
|
// src/actions/searchIssues.ts
|
|
1022
1373
|
import {
|
|
1023
|
-
ModelType as
|
|
1024
|
-
logger as
|
|
1374
|
+
ModelType as ModelType5,
|
|
1375
|
+
logger as logger6
|
|
1025
1376
|
} from "@elizaos/core";
|
|
1026
1377
|
var searchTemplate = `Extract search criteria from the user's request for Linear issues.
|
|
1027
1378
|
|
|
1028
1379
|
User request: "{{userMessage}}"
|
|
1029
1380
|
|
|
1030
|
-
|
|
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:
|
|
1031
1392
|
{
|
|
1032
|
-
"query": "
|
|
1033
|
-
"
|
|
1034
|
-
"
|
|
1035
|
-
"
|
|
1036
|
-
"
|
|
1037
|
-
"
|
|
1038
|
-
"hasAssignee": true/false
|
|
1039
|
-
"
|
|
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)
|
|
1040
1411
|
}
|
|
1041
1412
|
|
|
1042
|
-
Only include fields that are mentioned.
|
|
1413
|
+
Only include fields that are clearly mentioned or implied. For "my" issues, set assignees to ["me"].`;
|
|
1043
1414
|
var searchIssuesAction = {
|
|
1044
1415
|
name: "SEARCH_LINEAR_ISSUES",
|
|
1045
1416
|
description: "Search for issues in Linear with various filters",
|
|
1046
|
-
similes: ["search-linear-issues", "find-linear-issues", "query-linear-issues"],
|
|
1417
|
+
similes: ["search-linear-issues", "find-linear-issues", "query-linear-issues", "list-linear-issues"],
|
|
1047
1418
|
examples: [[
|
|
1048
1419
|
{
|
|
1049
1420
|
name: "User",
|
|
@@ -1062,13 +1433,27 @@ var searchIssuesAction = {
|
|
|
1062
1433
|
{
|
|
1063
1434
|
name: "User",
|
|
1064
1435
|
content: {
|
|
1065
|
-
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"
|
|
1066
1451
|
}
|
|
1067
1452
|
},
|
|
1068
1453
|
{
|
|
1069
1454
|
name: "Assistant",
|
|
1070
1455
|
content: {
|
|
1071
|
-
text: "I'll search for high priority issues
|
|
1456
|
+
text: "I'll search for high priority issues created this week.",
|
|
1072
1457
|
actions: ["SEARCH_LINEAR_ISSUES"]
|
|
1073
1458
|
}
|
|
1074
1459
|
}
|
|
@@ -1104,7 +1489,7 @@ var searchIssuesAction = {
|
|
|
1104
1489
|
filters = _options.filters;
|
|
1105
1490
|
} else {
|
|
1106
1491
|
const prompt = searchTemplate.replace("{{userMessage}}", content);
|
|
1107
|
-
const response = await runtime.useModel(
|
|
1492
|
+
const response = await runtime.useModel(ModelType5.TEXT_LARGE, {
|
|
1108
1493
|
prompt
|
|
1109
1494
|
});
|
|
1110
1495
|
if (!response) {
|
|
@@ -1115,25 +1500,79 @@ var searchIssuesAction = {
|
|
|
1115
1500
|
const parsed = JSON.parse(cleanedResponse);
|
|
1116
1501
|
filters = {
|
|
1117
1502
|
query: parsed.query,
|
|
1118
|
-
|
|
1119
|
-
assignee: parsed.assignee ? [parsed.assignee] : void 0,
|
|
1120
|
-
priority: parsed.priority ? [parsed.priority] : void 0,
|
|
1121
|
-
team: parsed.team,
|
|
1122
|
-
label: parsed.label ? [parsed.label] : void 0,
|
|
1123
|
-
limit: parsed.limit
|
|
1503
|
+
limit: parsed.limit || 10
|
|
1124
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
|
+
}
|
|
1125
1554
|
Object.keys(filters).forEach((key) => {
|
|
1126
1555
|
if (filters[key] === void 0) {
|
|
1127
1556
|
delete filters[key];
|
|
1128
1557
|
}
|
|
1129
1558
|
});
|
|
1130
1559
|
} catch (parseError) {
|
|
1131
|
-
|
|
1560
|
+
logger6.error("Failed to parse search filters:", parseError);
|
|
1132
1561
|
filters = { query: content };
|
|
1133
1562
|
}
|
|
1134
1563
|
}
|
|
1135
1564
|
}
|
|
1136
|
-
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;
|
|
1137
1576
|
const issues = await linearService.searchIssues(filters);
|
|
1138
1577
|
if (issues.length === 0) {
|
|
1139
1578
|
const noResultsMessage = "No issues found matching your search criteria.";
|
|
@@ -1153,37 +1592,47 @@ var searchIssuesAction = {
|
|
|
1153
1592
|
}
|
|
1154
1593
|
const issueList = await Promise.all(issues.map(async (issue, index) => {
|
|
1155
1594
|
const state = await issue.state;
|
|
1156
|
-
|
|
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"}`;
|
|
1157
1600
|
}));
|
|
1158
|
-
const issueText = issueList.join("\n");
|
|
1601
|
+
const issueText = issueList.join("\n\n");
|
|
1159
1602
|
const resultMessage = `\u{1F4CB} Found ${issues.length} issue${issues.length === 1 ? "" : "s"}:
|
|
1603
|
+
|
|
1160
1604
|
${issueText}`;
|
|
1161
1605
|
await callback?.({
|
|
1162
1606
|
text: resultMessage,
|
|
1163
1607
|
source: message.content.source
|
|
1164
1608
|
});
|
|
1165
1609
|
return {
|
|
1166
|
-
text: `Found ${issues.length} issue${issues.length === 1 ? "" : "s"}
|
|
1167
|
-
${issueText}`,
|
|
1610
|
+
text: `Found ${issues.length} issue${issues.length === 1 ? "" : "s"}`,
|
|
1168
1611
|
success: true,
|
|
1169
1612
|
data: {
|
|
1170
|
-
issues: issues.map((
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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
|
+
};
|
|
1180
1629
|
})),
|
|
1181
1630
|
filters,
|
|
1182
1631
|
count: issues.length
|
|
1183
1632
|
}
|
|
1184
1633
|
};
|
|
1185
1634
|
} catch (error) {
|
|
1186
|
-
|
|
1635
|
+
logger6.error("Failed to search issues:", error);
|
|
1187
1636
|
const errorMessage = `\u274C Failed to search issues: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1188
1637
|
await callback?.({
|
|
1189
1638
|
text: errorMessage,
|
|
@@ -1198,22 +1647,56 @@ ${issueText}`,
|
|
|
1198
1647
|
};
|
|
1199
1648
|
|
|
1200
1649
|
// src/actions/createComment.ts
|
|
1201
|
-
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.`;
|
|
1202
1671
|
var createCommentAction = {
|
|
1203
1672
|
name: "CREATE_LINEAR_COMMENT",
|
|
1204
|
-
description: "
|
|
1205
|
-
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"],
|
|
1206
1675
|
examples: [[
|
|
1207
1676
|
{
|
|
1208
1677
|
name: "User",
|
|
1209
1678
|
content: {
|
|
1210
|
-
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"
|
|
1211
1694
|
}
|
|
1212
1695
|
},
|
|
1213
1696
|
{
|
|
1214
1697
|
name: "Assistant",
|
|
1215
1698
|
content: {
|
|
1216
|
-
text: "I'll add that comment to issue
|
|
1699
|
+
text: "I'll add that comment to the login bug issue.",
|
|
1217
1700
|
actions: ["CREATE_LINEAR_COMMENT"]
|
|
1218
1701
|
}
|
|
1219
1702
|
}
|
|
@@ -1221,13 +1704,13 @@ var createCommentAction = {
|
|
|
1221
1704
|
{
|
|
1222
1705
|
name: "User",
|
|
1223
1706
|
content: {
|
|
1224
|
-
text: "
|
|
1707
|
+
text: "Reply to COM2-7: Thanks for the update, I'll look into it"
|
|
1225
1708
|
}
|
|
1226
1709
|
},
|
|
1227
1710
|
{
|
|
1228
1711
|
name: "Assistant",
|
|
1229
1712
|
content: {
|
|
1230
|
-
text: "I'll
|
|
1713
|
+
text: "I'll add your reply to issue COM2-7.",
|
|
1231
1714
|
actions: ["CREATE_LINEAR_COMMENT"]
|
|
1232
1715
|
}
|
|
1233
1716
|
}
|
|
@@ -1248,7 +1731,7 @@ var createCommentAction = {
|
|
|
1248
1731
|
}
|
|
1249
1732
|
const content = message.content.text;
|
|
1250
1733
|
if (!content) {
|
|
1251
|
-
const errorMessage = "Please provide a message with the issue
|
|
1734
|
+
const errorMessage = "Please provide a message with the issue and comment content.";
|
|
1252
1735
|
await callback?.({
|
|
1253
1736
|
text: errorMessage,
|
|
1254
1737
|
source: message.content.source
|
|
@@ -1258,9 +1741,108 @@ var createCommentAction = {
|
|
|
1258
1741
|
success: false
|
|
1259
1742
|
};
|
|
1260
1743
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
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.";
|
|
1264
1846
|
await callback?.({
|
|
1265
1847
|
text: errorMessage,
|
|
1266
1848
|
source: message.content.source
|
|
@@ -1270,27 +1852,29 @@ var createCommentAction = {
|
|
|
1270
1852
|
success: false
|
|
1271
1853
|
};
|
|
1272
1854
|
}
|
|
1273
|
-
const
|
|
1274
|
-
const issue = await linearService.getIssue(issueIdentifier);
|
|
1855
|
+
const issue = await linearService.getIssue(issueId);
|
|
1275
1856
|
const comment = await linearService.createComment({
|
|
1276
1857
|
issueId: issue.id,
|
|
1277
|
-
body: commentBody
|
|
1858
|
+
body: commentBody
|
|
1278
1859
|
});
|
|
1279
|
-
const successMessage = `\u2705 Comment added to issue ${
|
|
1860
|
+
const successMessage = `\u2705 Comment added to issue ${issue.identifier}: "${commentBody}"`;
|
|
1280
1861
|
await callback?.({
|
|
1281
1862
|
text: successMessage,
|
|
1282
1863
|
source: message.content.source
|
|
1283
1864
|
});
|
|
1284
1865
|
return {
|
|
1285
|
-
text: `
|
|
1866
|
+
text: `Added comment to issue ${issue.identifier}`,
|
|
1286
1867
|
success: true,
|
|
1287
1868
|
data: {
|
|
1288
1869
|
commentId: comment.id,
|
|
1289
|
-
issueId: issue.id
|
|
1870
|
+
issueId: issue.id,
|
|
1871
|
+
issueIdentifier: issue.identifier,
|
|
1872
|
+
commentBody,
|
|
1873
|
+
createdAt: comment.createdAt
|
|
1290
1874
|
}
|
|
1291
1875
|
};
|
|
1292
1876
|
} catch (error) {
|
|
1293
|
-
|
|
1877
|
+
logger7.error("Failed to create comment:", error);
|
|
1294
1878
|
const errorMessage = `\u274C Failed to create comment: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1295
1879
|
await callback?.({
|
|
1296
1880
|
text: errorMessage,
|
|
@@ -1305,11 +1889,34 @@ var createCommentAction = {
|
|
|
1305
1889
|
};
|
|
1306
1890
|
|
|
1307
1891
|
// src/actions/listTeams.ts
|
|
1308
|
-
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.`;
|
|
1309
1916
|
var listTeamsAction = {
|
|
1310
1917
|
name: "LIST_LINEAR_TEAMS",
|
|
1311
|
-
description: "List
|
|
1312
|
-
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"],
|
|
1313
1920
|
examples: [[
|
|
1314
1921
|
{
|
|
1315
1922
|
name: "User",
|
|
@@ -1328,13 +1935,27 @@ var listTeamsAction = {
|
|
|
1328
1935
|
{
|
|
1329
1936
|
name: "User",
|
|
1330
1937
|
content: {
|
|
1331
|
-
text: "
|
|
1938
|
+
text: "Which engineering teams do we have?"
|
|
1332
1939
|
}
|
|
1333
1940
|
},
|
|
1334
1941
|
{
|
|
1335
1942
|
name: "Assistant",
|
|
1336
1943
|
content: {
|
|
1337
|
-
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.",
|
|
1338
1959
|
actions: ["LIST_LINEAR_TEAMS"]
|
|
1339
1960
|
}
|
|
1340
1961
|
}
|
|
@@ -1353,9 +1974,51 @@ var listTeamsAction = {
|
|
|
1353
1974
|
if (!linearService) {
|
|
1354
1975
|
throw new Error("Linear service not available");
|
|
1355
1976
|
}
|
|
1356
|
-
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
|
+
}
|
|
1357
2020
|
if (teams.length === 0) {
|
|
1358
|
-
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.";
|
|
1359
2022
|
await callback?.({
|
|
1360
2023
|
text: noTeamsMessage,
|
|
1361
2024
|
source: message.content.source
|
|
@@ -1368,31 +2031,68 @@ var listTeamsAction = {
|
|
|
1368
2031
|
}
|
|
1369
2032
|
};
|
|
1370
2033
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
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
|
+
|
|
1375
2070
|
${teamList}`;
|
|
1376
2071
|
await callback?.({
|
|
1377
2072
|
text: resultMessage,
|
|
1378
2073
|
source: message.content.source
|
|
1379
2074
|
});
|
|
1380
2075
|
return {
|
|
1381
|
-
text: `Found ${teams.length} team${teams.length === 1 ? "" : "s"}
|
|
1382
|
-
${teamList}`,
|
|
2076
|
+
text: `Found ${teams.length} team${teams.length === 1 ? "" : "s"}`,
|
|
1383
2077
|
success: true,
|
|
1384
2078
|
data: {
|
|
1385
|
-
teams:
|
|
2079
|
+
teams: teamsWithDetails.map((t) => ({
|
|
1386
2080
|
id: t.id,
|
|
1387
2081
|
name: t.name,
|
|
1388
2082
|
key: t.key,
|
|
1389
|
-
description: t.description
|
|
2083
|
+
description: t.description,
|
|
2084
|
+
memberCount: t.memberCount,
|
|
2085
|
+
projectCount: t.projectCount
|
|
1390
2086
|
})),
|
|
1391
|
-
count: teams.length
|
|
2087
|
+
count: teams.length,
|
|
2088
|
+
filters: {
|
|
2089
|
+
name: nameFilter,
|
|
2090
|
+
specific: specificTeam
|
|
2091
|
+
}
|
|
1392
2092
|
}
|
|
1393
2093
|
};
|
|
1394
2094
|
} catch (error) {
|
|
1395
|
-
|
|
2095
|
+
logger8.error("Failed to list teams:", error);
|
|
1396
2096
|
const errorMessage = `\u274C Failed to list teams: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1397
2097
|
await callback?.({
|
|
1398
2098
|
text: errorMessage,
|
|
@@ -1407,11 +2107,40 @@ ${teamList}`,
|
|
|
1407
2107
|
};
|
|
1408
2108
|
|
|
1409
2109
|
// src/actions/listProjects.ts
|
|
1410
|
-
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.`;
|
|
1411
2140
|
var listProjectsAction = {
|
|
1412
2141
|
name: "LIST_LINEAR_PROJECTS",
|
|
1413
|
-
description: "List
|
|
1414
|
-
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"],
|
|
1415
2144
|
examples: [[
|
|
1416
2145
|
{
|
|
1417
2146
|
name: "User",
|
|
@@ -1430,13 +2159,27 @@ var listProjectsAction = {
|
|
|
1430
2159
|
{
|
|
1431
2160
|
name: "User",
|
|
1432
2161
|
content: {
|
|
1433
|
-
text: "What projects do we have?"
|
|
2162
|
+
text: "What active projects do we have?"
|
|
1434
2163
|
}
|
|
1435
2164
|
},
|
|
1436
2165
|
{
|
|
1437
2166
|
name: "Assistant",
|
|
1438
2167
|
content: {
|
|
1439
|
-
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.",
|
|
1440
2183
|
actions: ["LIST_LINEAR_PROJECTS"]
|
|
1441
2184
|
}
|
|
1442
2185
|
}
|
|
@@ -1455,9 +2198,79 @@ var listProjectsAction = {
|
|
|
1455
2198
|
if (!linearService) {
|
|
1456
2199
|
throw new Error("Linear service not available");
|
|
1457
2200
|
}
|
|
1458
|
-
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
|
+
}
|
|
1459
2272
|
if (projects.length === 0) {
|
|
1460
|
-
const noProjectsMessage = "No projects found in Linear.";
|
|
2273
|
+
const noProjectsMessage = teamId ? "No projects found for the specified team." : "No projects found in Linear.";
|
|
1461
2274
|
await callback?.({
|
|
1462
2275
|
text: noProjectsMessage,
|
|
1463
2276
|
source: message.content.source
|
|
@@ -1474,25 +2287,37 @@ var listProjectsAction = {
|
|
|
1474
2287
|
projects.map(async (project) => {
|
|
1475
2288
|
const teamsQuery = await project.teams();
|
|
1476
2289
|
const teams = await teamsQuery.nodes;
|
|
2290
|
+
const lead = await project.lead;
|
|
1477
2291
|
return {
|
|
1478
2292
|
...project,
|
|
1479
|
-
teamsList: teams
|
|
2293
|
+
teamsList: teams,
|
|
2294
|
+
leadUser: lead
|
|
1480
2295
|
};
|
|
1481
2296
|
})
|
|
1482
2297
|
);
|
|
1483
2298
|
const projectList = projectsWithDetails.map((project, index) => {
|
|
1484
2299
|
const teamNames = project.teamsList.map((t) => t.name).join(", ") || "No teams";
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
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
|
+
|
|
1488
2314
|
${projectList}`;
|
|
1489
2315
|
await callback?.({
|
|
1490
2316
|
text: resultMessage,
|
|
1491
2317
|
source: message.content.source
|
|
1492
2318
|
});
|
|
1493
2319
|
return {
|
|
1494
|
-
text: `Found ${projects.length} project${projects.length === 1 ? "" : "s"}
|
|
1495
|
-
${projectList}`,
|
|
2320
|
+
text: `Found ${projects.length} project${projects.length === 1 ? "" : "s"}`,
|
|
1496
2321
|
success: true,
|
|
1497
2322
|
data: {
|
|
1498
2323
|
projects: projectsWithDetails.map((p) => ({
|
|
@@ -1505,16 +2330,25 @@ ${projectList}`,
|
|
|
1505
2330
|
name: t.name,
|
|
1506
2331
|
key: t.key
|
|
1507
2332
|
})),
|
|
2333
|
+
lead: p.leadUser ? {
|
|
2334
|
+
id: p.leadUser.id,
|
|
2335
|
+
name: p.leadUser.name,
|
|
2336
|
+
email: p.leadUser.email
|
|
2337
|
+
} : null,
|
|
1508
2338
|
state: p.state,
|
|
1509
2339
|
progress: p.progress,
|
|
1510
2340
|
startDate: p.startDate,
|
|
1511
2341
|
targetDate: p.targetDate
|
|
1512
2342
|
})),
|
|
1513
|
-
count: projects.length
|
|
2343
|
+
count: projects.length,
|
|
2344
|
+
filters: {
|
|
2345
|
+
team: teamId,
|
|
2346
|
+
state: stateFilter
|
|
2347
|
+
}
|
|
1514
2348
|
}
|
|
1515
2349
|
};
|
|
1516
2350
|
} catch (error) {
|
|
1517
|
-
|
|
2351
|
+
logger9.error("Failed to list projects:", error);
|
|
1518
2352
|
const errorMessage = `\u274C Failed to list projects: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1519
2353
|
await callback?.({
|
|
1520
2354
|
text: errorMessage,
|
|
@@ -1529,11 +2363,39 @@ ${projectList}`,
|
|
|
1529
2363
|
};
|
|
1530
2364
|
|
|
1531
2365
|
// src/actions/getActivity.ts
|
|
1532
|
-
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.`;
|
|
1533
2395
|
var getActivityAction = {
|
|
1534
2396
|
name: "GET_LINEAR_ACTIVITY",
|
|
1535
|
-
description: "Get recent Linear activity",
|
|
1536
|
-
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"],
|
|
1537
2399
|
examples: [[
|
|
1538
2400
|
{
|
|
1539
2401
|
name: "User",
|
|
@@ -1544,7 +2406,21 @@ var getActivityAction = {
|
|
|
1544
2406
|
{
|
|
1545
2407
|
name: "Assistant",
|
|
1546
2408
|
content: {
|
|
1547
|
-
text: "I'll
|
|
2409
|
+
text: "I'll show you the recent Linear activity.",
|
|
2410
|
+
actions: ["GET_LINEAR_ACTIVITY"]
|
|
2411
|
+
}
|
|
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.",
|
|
1548
2424
|
actions: ["GET_LINEAR_ACTIVITY"]
|
|
1549
2425
|
}
|
|
1550
2426
|
}
|
|
@@ -1552,13 +2428,13 @@ var getActivityAction = {
|
|
|
1552
2428
|
{
|
|
1553
2429
|
name: "User",
|
|
1554
2430
|
content: {
|
|
1555
|
-
text: "
|
|
2431
|
+
text: "Show me what issues John created this week"
|
|
1556
2432
|
}
|
|
1557
2433
|
},
|
|
1558
2434
|
{
|
|
1559
2435
|
name: "Assistant",
|
|
1560
2436
|
content: {
|
|
1561
|
-
text: "
|
|
2437
|
+
text: "I'll find the issues John created this week.",
|
|
1562
2438
|
actions: ["GET_LINEAR_ACTIVITY"]
|
|
1563
2439
|
}
|
|
1564
2440
|
}
|
|
@@ -1577,9 +2453,74 @@ var getActivityAction = {
|
|
|
1577
2453
|
if (!linearService) {
|
|
1578
2454
|
throw new Error("Linear service not available");
|
|
1579
2455
|
}
|
|
1580
|
-
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);
|
|
1581
2522
|
if (activity.length === 0) {
|
|
1582
|
-
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.";
|
|
1583
2524
|
await callback?.({
|
|
1584
2525
|
text: noActivityMessage,
|
|
1585
2526
|
source: message.content.source
|
|
@@ -1592,28 +2533,35 @@ var getActivityAction = {
|
|
|
1592
2533
|
}
|
|
1593
2534
|
};
|
|
1594
2535
|
}
|
|
1595
|
-
const activityText = activity.
|
|
1596
|
-
const
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
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
|
+
|
|
1600
2548
|
${activityText}`;
|
|
1601
2549
|
await callback?.({
|
|
1602
2550
|
text: resultMessage,
|
|
1603
2551
|
source: message.content.source
|
|
1604
2552
|
});
|
|
1605
2553
|
return {
|
|
1606
|
-
text: `
|
|
1607
|
-
${activityText}`,
|
|
2554
|
+
text: `Found ${activity.length} activity item${activity.length === 1 ? "" : "s"}`,
|
|
1608
2555
|
success: true,
|
|
1609
2556
|
data: {
|
|
1610
|
-
activity
|
|
2557
|
+
activity,
|
|
2558
|
+
filters,
|
|
1611
2559
|
count: activity.length
|
|
1612
2560
|
}
|
|
1613
2561
|
};
|
|
1614
2562
|
} catch (error) {
|
|
1615
|
-
|
|
1616
|
-
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"}`;
|
|
1617
2565
|
await callback?.({
|
|
1618
2566
|
text: errorMessage,
|
|
1619
2567
|
source: message.content.source
|
|
@@ -1627,7 +2575,7 @@ ${activityText}`,
|
|
|
1627
2575
|
};
|
|
1628
2576
|
|
|
1629
2577
|
// src/actions/clearActivity.ts
|
|
1630
|
-
import { logger as
|
|
2578
|
+
import { logger as logger11 } from "@elizaos/core";
|
|
1631
2579
|
var clearActivityAction = {
|
|
1632
2580
|
name: "CLEAR_LINEAR_ACTIVITY",
|
|
1633
2581
|
description: "Clear the Linear activity log",
|
|
@@ -1686,7 +2634,7 @@ var clearActivityAction = {
|
|
|
1686
2634
|
success: true
|
|
1687
2635
|
};
|
|
1688
2636
|
} catch (error) {
|
|
1689
|
-
|
|
2637
|
+
logger11.error("Failed to clear Linear activity:", error);
|
|
1690
2638
|
const errorMessage = `\u274C Failed to clear Linear activity: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
1691
2639
|
await callback?.({
|
|
1692
2640
|
text: errorMessage,
|
|
@@ -1709,6 +2657,7 @@ var linearPlugin = {
|
|
|
1709
2657
|
createIssueAction,
|
|
1710
2658
|
getIssueAction,
|
|
1711
2659
|
updateIssueAction,
|
|
2660
|
+
deleteIssueAction,
|
|
1712
2661
|
searchIssuesAction,
|
|
1713
2662
|
createCommentAction,
|
|
1714
2663
|
listTeamsAction,
|