@elizaos/plugin-linear 2.0.0-alpha.6 → 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +1808 -1187
  2. package/dist/index.js.map +29 -21
  3. package/package.json +8 -8
package/dist/index.js CHANGED
@@ -1,11 +1,173 @@
1
+ // src/index.ts
2
+ import { getConnectorAccountManager, logger as logger14, promoteSubactionsToActions } from "@elizaos/core";
3
+
4
+ // src/accounts.ts
5
+ var DEFAULT_LINEAR_ACCOUNT_ID = "default";
6
+ var DEFAULT_LINEAR_ACCOUNT_ROLE = "OWNER";
7
+ function nonEmptyString(value) {
8
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
9
+ }
10
+ function readSetting(runtime, key) {
11
+ return nonEmptyString(runtime.getSetting(key));
12
+ }
13
+ function normalizeLinearAccountId(value) {
14
+ return nonEmptyString(value) ?? DEFAULT_LINEAR_ACCOUNT_ID;
15
+ }
16
+ function resolveLinearAccountId(runtime, options) {
17
+ const requested = nonEmptyString(options?.accountId) ?? nonEmptyString(options?.linearAccountId);
18
+ if (requested)
19
+ return requested;
20
+ const configuredDefault = readSetting(runtime, "LINEAR_DEFAULT_ACCOUNT_ID") ?? readSetting(runtime, "LINEAR_ACCOUNT_ID");
21
+ const accounts = readLinearAccounts(runtime);
22
+ const defaultAccount = resolveLinearDefaultAccount(accounts, configuredDefault);
23
+ return defaultAccount?.accountId ?? normalizeLinearAccountId(configuredDefault);
24
+ }
25
+ function parseAccountsJson(raw) {
26
+ if (!raw)
27
+ return [];
28
+ try {
29
+ const parsed = JSON.parse(raw);
30
+ if (Array.isArray(parsed)) {
31
+ return parsed.filter((item) => Boolean(item) && typeof item === "object" && !Array.isArray(item));
32
+ }
33
+ if (parsed && typeof parsed === "object") {
34
+ return Object.entries(parsed).filter(([, value]) => value && typeof value === "object").map(([id, value]) => ({
35
+ ...value,
36
+ accountId: value.accountId ?? id
37
+ }));
38
+ }
39
+ } catch {
40
+ return [];
41
+ }
42
+ return [];
43
+ }
44
+ function readRawField(record, keys) {
45
+ const credentials = record.credentials && typeof record.credentials === "object" ? record.credentials : {};
46
+ const metadata = record.metadata && typeof record.metadata === "object" ? record.metadata : {};
47
+ const settings = record.settings && typeof record.settings === "object" ? record.settings : {};
48
+ for (const source of [record, credentials, metadata, settings]) {
49
+ for (const key of keys) {
50
+ const value = nonEmptyString(source[key]);
51
+ if (value)
52
+ return value;
53
+ }
54
+ }
55
+ return;
56
+ }
57
+ function accountFromRecord(record) {
58
+ const accountId = normalizeLinearAccountId(record.accountId ?? record.id ?? record.name);
59
+ const apiKey = readRawField(record, [
60
+ "LINEAR_API_KEY",
61
+ "apiKey",
62
+ "token",
63
+ "accessToken",
64
+ "access"
65
+ ]);
66
+ if (!apiKey)
67
+ return null;
68
+ return {
69
+ accountId,
70
+ role: DEFAULT_LINEAR_ACCOUNT_ROLE,
71
+ apiKey,
72
+ workspaceId: readRawField(record, ["LINEAR_WORKSPACE_ID", "workspaceId"]),
73
+ defaultTeamKey: readRawField(record, ["LINEAR_DEFAULT_TEAM_KEY", "defaultTeamKey"]),
74
+ label: nonEmptyString(record.label ?? record.displayName)
75
+ };
76
+ }
77
+ function addAccount(accounts, account) {
78
+ if (account) {
79
+ accounts.set(account.accountId, account);
80
+ }
81
+ }
82
+ function readLinearAccounts(runtime) {
83
+ const accounts = new Map;
84
+ const characterConfig = runtime.character?.settings?.linear;
85
+ const characterAccounts = characterConfig?.accounts;
86
+ if (Array.isArray(characterAccounts)) {
87
+ for (const item of characterAccounts) {
88
+ if (item && typeof item === "object") {
89
+ addAccount(accounts, accountFromRecord(item));
90
+ }
91
+ }
92
+ } else if (characterAccounts && typeof characterAccounts === "object") {
93
+ for (const [id, value] of Object.entries(characterAccounts)) {
94
+ if (value && typeof value === "object") {
95
+ addAccount(accounts, accountFromRecord({
96
+ ...value,
97
+ accountId: value.accountId ?? id
98
+ }));
99
+ }
100
+ }
101
+ }
102
+ for (const record of parseAccountsJson(readSetting(runtime, "LINEAR_ACCOUNTS"))) {
103
+ addAccount(accounts, accountFromRecord(record));
104
+ }
105
+ const apiKey = readSetting(runtime, "LINEAR_API_KEY");
106
+ if (apiKey) {
107
+ addAccount(accounts, {
108
+ accountId: normalizeLinearAccountId(readSetting(runtime, "LINEAR_ACCOUNT_ID") ?? readSetting(runtime, "LINEAR_DEFAULT_ACCOUNT_ID")),
109
+ role: DEFAULT_LINEAR_ACCOUNT_ROLE,
110
+ apiKey,
111
+ workspaceId: readSetting(runtime, "LINEAR_WORKSPACE_ID"),
112
+ defaultTeamKey: readSetting(runtime, "LINEAR_DEFAULT_TEAM_KEY")
113
+ });
114
+ }
115
+ return Array.from(accounts.values());
116
+ }
117
+ function resolveLinearAccount(accounts, accountId) {
118
+ return accounts.find((account) => account.accountId === accountId) ?? null;
119
+ }
120
+ function resolveLinearDefaultAccount(accounts, accountId) {
121
+ const normalized = normalizeLinearAccountId(accountId);
122
+ return resolveLinearAccount(accounts, normalized) ?? resolveLinearAccount(accounts, DEFAULT_LINEAR_ACCOUNT_ID) ?? accounts.find((account) => account.role === DEFAULT_LINEAR_ACCOUNT_ROLE) ?? accounts[0] ?? null;
123
+ }
124
+ function hasLinearAccountConfig(runtime, options) {
125
+ const accountId = resolveLinearAccountId(runtime, options);
126
+ return Boolean(resolveLinearAccount(readLinearAccounts(runtime), accountId));
127
+ }
128
+
129
+ // src/actions/account-options.ts
130
+ function getLinearActionOptions(options) {
131
+ const direct = options ?? {};
132
+ const parameters = direct.parameters && typeof direct.parameters === "object" ? direct.parameters : {};
133
+ return { ...direct, ...parameters };
134
+ }
135
+ function getLinearAccountId(runtime, options) {
136
+ return resolveLinearAccountId(runtime, getLinearActionOptions(options));
137
+ }
138
+ var linearAccountIdParameter = {
139
+ name: "accountId",
140
+ description: "Optional Linear account id from LINEAR_ACCOUNTS. Defaults to LINEAR_DEFAULT_ACCOUNT_ID or the legacy single API key.",
141
+ required: false,
142
+ schema: { type: "string" }
143
+ };
144
+
1
145
  // src/actions/clearActivity.ts
2
146
  import {
3
- logger
147
+ logger,
148
+ requireConfirmation
4
149
  } from "@elizaos/core";
150
+
151
+ // src/actions/validate-linear-intent.ts
152
+ async function validateLinearActionIntent(runtime, _message, _state, _spec) {
153
+ try {
154
+ return hasLinearAccountConfig(runtime);
155
+ } catch {
156
+ return false;
157
+ }
158
+ }
159
+
160
+ // src/actions/clearActivity.ts
161
+ var CLEAR_ACTIVITY_TIMEOUT_MS = 1e4;
5
162
  var clearActivityAction = {
6
163
  name: "CLEAR_LINEAR_ACTIVITY",
7
- description: "Clear the Linear activity log",
164
+ contexts: ["tasks", "connectors", "automation"],
165
+ contextGate: { anyOf: ["tasks", "connectors", "automation"] },
166
+ roleGate: { minRole: "USER" },
167
+ description: "Clear the cached Linear activity log for the connected Linear account. Use when the user asks to reset, wipe, or refresh their Linear activity history before pulling a fresh view of recent issue and comment events.",
168
+ descriptionCompressed: "clear Linear activity log",
8
169
  similes: ["clear-linear-activity", "reset-linear-activity", "delete-linear-activity"],
170
+ parameters: [linearAccountIdParameter],
9
171
  examples: [
10
172
  [
11
173
  {
@@ -38,38 +200,45 @@ var clearActivityAction = {
38
200
  }
39
201
  ]
40
202
  ],
41
- validate: async (runtime, message, state, options) => {
42
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
43
- const __avText = __avTextRaw.toLowerCase();
44
- const __avKeywords = ["clear", "linear", "activity"];
45
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
46
- const __avRegex = /\b(?:clear|linear|activity)\b/i;
47
- const __avRegexOk = __avRegex.test(__avText);
48
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
49
- const __avExpectedSource = "";
50
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
51
- const __avOptions = options && typeof options === "object" ? options : {};
52
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
53
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
54
- return false;
55
- }
56
- const __avLegacyValidate = async (runtime2, _message, _state) => {
57
- const apiKey = runtime2.getSetting("LINEAR_API_KEY");
58
- return !!apiKey;
59
- };
60
- try {
61
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
62
- } catch {
63
- return false;
64
- }
65
- },
203
+ validate: async (runtime, message, state) => validateLinearActionIntent(runtime, message, state, {
204
+ keywords: ["clear", "linear", "activity"],
205
+ regexAlternation: "clear|linear|activity"
206
+ }),
66
207
  async handler(runtime, message, _state, _options, callback) {
67
208
  try {
68
209
  const linearService = runtime.getService("linear");
69
210
  if (!linearService) {
70
211
  throw new Error("Linear service not available");
71
212
  }
72
- await linearService.clearActivityLog();
213
+ const accountId = getLinearAccountId(runtime, _options);
214
+ const decision = await requireConfirmation({
215
+ runtime,
216
+ message,
217
+ actionName: "CLEAR_LINEAR_ACTIVITY",
218
+ pendingKey: `clear_log:${accountId}`,
219
+ prompt: 'Clear the Linear activity log? Reply "yes" to confirm.',
220
+ callback
221
+ });
222
+ if (decision.status === "pending") {
223
+ return {
224
+ text: "Awaiting confirmation to clear Linear activity.",
225
+ success: true,
226
+ data: { awaitingUserInput: true }
227
+ };
228
+ }
229
+ if (decision.status === "cancelled") {
230
+ const cancelMessage = "Clear of Linear activity cancelled.";
231
+ await callback?.({ text: cancelMessage, source: message.content.source });
232
+ return {
233
+ text: cancelMessage,
234
+ success: true,
235
+ data: { cancelled: true }
236
+ };
237
+ }
238
+ await Promise.race([
239
+ linearService.clearActivityLog(accountId),
240
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Linear clear activity timeout")), CLEAR_ACTIVITY_TIMEOUT_MS))
241
+ ]);
73
242
  const successMessage = "✅ Linear activity log has been cleared.";
74
243
  await callback?.({
75
244
  text: successMessage,
@@ -100,7 +269,7 @@ import {
100
269
  ModelType
101
270
  } from "@elizaos/core";
102
271
 
103
- // src/generated/prompts/typescript/prompts.ts
272
+ // src/prompts.ts
104
273
  var createCommentTemplate = `Extract comment details from the user's request to add a comment to a Linear issue.
105
274
 
106
275
  User request: "{{userMessage}}"
@@ -112,40 +281,41 @@ The user might express this in various ways:
112
281
  - "Reply to COM2-7: Thanks for the update"
113
282
  - "Let the payment issue know that it's blocked by API changes"
114
283
 
115
- Return ONLY a JSON object:
284
+ Respond with JSON only. Use this shape:
116
285
  {
117
- "issueId": "Direct issue ID if explicitly mentioned (e.g., ENG-123)",
118
- "issueDescription": "Description/keywords of the issue if no ID provided",
286
+ "issueId": "Direct issue ID if explicitly mentioned, for example ENG-123",
287
+ "issueDescription": "Description or keywords of the issue if no ID was provided",
119
288
  "commentBody": "The actual comment content to add",
120
- "commentType": "note/reply/update/question/feedback (inferred from context)"
289
+ "commentType": "note|reply|update|question|feedback"
121
290
  }
122
291
 
123
- Extract the core message the user wants to convey as the comment body.`;
292
+ Extract the core message the user wants to convey as the comment body.
293
+ Omit unknown fields. Output only the JSON object, with no prose before or after it.`;
124
294
  var createIssueTemplate = `Given the user's request, extract the information needed to create a Linear issue.
125
295
 
126
296
  User request: "{{userMessage}}"
127
297
 
128
- Extract and return ONLY a JSON object (no markdown formatting, no code blocks) with the following structure:
298
+ Respond with JSON only. Use this shape:
129
299
  {
130
300
  "title": "Brief, clear issue title",
131
- "description": "Detailed description of the issue (optional, omit or use null if not provided)",
132
- "teamKey": "Team key if mentioned (e.g., ENG, PROD) - omit or use null if not mentioned",
133
- "priority": "Priority level if mentioned (1=urgent, 2=high, 3=normal, 4=low) - omit or use null if not mentioned",
134
- "labels": ["label1", "label2"] (if any labels are mentioned, empty array if none),
135
- "assignee": "Assignee username or email if mentioned - omit or use null if not mentioned"
301
+ "description": "Detailed description of the issue",
302
+ "teamKey": "Team key if mentioned, such as ENG or PROD",
303
+ "priority": 3,
304
+ "labels": ["label"],
305
+ "assignee": "Assignee username or email if mentioned"
136
306
  }
137
307
 
138
- Return only the JSON object, no other text.`;
308
+ Omit optional fields when they are not provided. Output only the JSON object, with no prose before or after it.`;
139
309
  var deleteIssueTemplate = `Given the user's request to delete/archive a Linear issue, extract the issue identifier.
140
310
 
141
311
  User request: "{{userMessage}}"
142
312
 
143
- Extract and return ONLY a JSON object (no markdown formatting, no code blocks) with:
313
+ Respond with JSON only:
144
314
  {
145
- "issueId": "The issue identifier (e.g., ENG-123, COM2-7)"
315
+ "issueId": "The issue identifier, such as ENG-123 or COM2-7"
146
316
  }
147
317
 
148
- Return only the JSON object, no other text.`;
318
+ Output only the JSON object, with no prose before or after it.`;
149
319
  var getActivityTemplate = `Extract activity filter criteria from the user's request.
150
320
 
151
321
  User request: "{{userMessage}}"
@@ -158,22 +328,22 @@ The user might ask for activity in various ways:
158
328
  - "Recent comment activity" → action type + recency
159
329
  - "Failed operations this week" → success filter + time range
160
330
 
161
- Return ONLY a JSON object:
331
+ Respond with JSON only. Use this shape:
162
332
  {
163
333
  "timeRange": {
164
- "period": "today/yesterday/this-week/last-week/this-month",
165
- "from": "ISO datetime if specific",
166
- "to": "ISO datetime if specific"
334
+ "period": "today|yesterday|this-week|last-week|this-month",
335
+ "from": "ISO datetime if a specific start is mentioned",
336
+ "to": "ISO datetime if a specific end is mentioned"
167
337
  },
168
- "actionTypes": ["create_issue/update_issue/delete_issue/create_comment/search_issues/etc"],
169
- "resourceTypes": ["issue/project/comment/team"],
170
- "resourceId": "Specific resource ID if mentioned (e.g., ENG-123)",
171
- "user": "User name or 'me' for current user",
172
- "successFilter": "success/failed/all",
173
- "limit": number (default 10)
338
+ "actionTypes": ["create_issue"],
339
+ "resourceTypes": ["issue"],
340
+ "resourceId": "Specific resource ID if mentioned, such as ENG-123",
341
+ "user": "User name, or me for current user",
342
+ "successFilter": "success|failed|all",
343
+ "limit": 10
174
344
  }
175
345
 
176
- Only include fields that are clearly mentioned.`;
346
+ Only include fields that are clearly mentioned. Output only the JSON object, with no prose before or after it.`;
177
347
  var getIssueTemplate = `Extract issue identification from the user's request.
178
348
 
179
349
  User request: "{{userMessage}}"
@@ -185,73 +355,25 @@ The user might reference an issue by:
185
355
  - Recency (e.g., "the latest bug", "most recent issue")
186
356
  - Team context (e.g., "newest issue in ELIZA team")
187
357
 
188
- Return ONLY a JSON object:
358
+ Respond with JSON only. Use directId when an issue ID is explicitly mentioned:
359
+ {
360
+ "directId": "Issue ID such as ENG-123"
361
+ }
362
+
363
+ When no issue ID is provided, use searchBy fields:
189
364
  {
190
- "directId": "Issue ID if explicitly mentioned (e.g., ENG-123)",
191
365
  "searchBy": {
192
366
  "title": "Keywords from issue title if mentioned",
193
- "assignee": "Name/email of assignee if mentioned",
194
- "priority": "Priority level if mentioned (urgent/high/normal/low or 1-4)",
367
+ "assignee": "Name or email of assignee if mentioned",
368
+ "priority": "urgent|high|normal|low|1|2|3|4",
195
369
  "team": "Team name or key if mentioned",
196
- "state": "Issue state if mentioned (todo/in-progress/done)",
197
- "recency": "latest/newest/recent/last if mentioned",
198
- "type": "bug/feature/task if mentioned"
370
+ "state": "Issue state if mentioned, such as todo, in-progress, or done",
371
+ "recency": "latest|newest|recent|last",
372
+ "type": "bug|feature|task"
199
373
  }
200
374
  }
201
375
 
202
- Only include fields that are clearly mentioned or implied.`;
203
- var listProjectsTemplate = `Extract project filter criteria from the user's request.
204
-
205
- User request: "{{userMessage}}"
206
-
207
- The user might ask for projects in various ways:
208
- - "Show me all projects" → list all projects
209
- - "Active projects" → filter by state (active/planned/completed)
210
- - "Projects due this quarter" → filter by target date
211
- - "Which projects is Sarah managing?" → filter by lead/owner
212
- - "Projects with high priority issues" → filter by contained issue priority
213
- - "Projects for the engineering team" → filter by team
214
- - "Completed projects" → filter by state
215
- - "Projects starting next month" → filter by start date
216
-
217
- Return ONLY a JSON object:
218
- {
219
- "teamFilter": "Team name or key if mentioned",
220
- "stateFilter": "active/planned/completed/all",
221
- "dateFilter": {
222
- "type": "due/starting",
223
- "period": "this-week/this-month/this-quarter/next-month/next-quarter",
224
- "from": "ISO date if specific",
225
- "to": "ISO date if specific"
226
- },
227
- "leadFilter": "Project lead name if mentioned",
228
- "showAll": true/false (true if user explicitly asks for "all")
229
- }
230
-
231
- Only include fields that are clearly mentioned.`;
232
- var listTeamsTemplate = `Extract team filter criteria from the user's request.
233
-
234
- User request: "{{userMessage}}"
235
-
236
- The user might ask for teams in various ways:
237
- - "Show me all teams" → list all teams
238
- - "Engineering teams" → filter by teams with engineering in name/description
239
- - "List teams I'm part of" → filter by membership
240
- - "Which teams work on the mobile app?" → filter by description/focus
241
- - "Show me the ELIZA team details" → specific team lookup
242
- - "Active teams" → teams with recent activity
243
- - "Frontend and backend teams" → multiple team types
244
-
245
- Return ONLY a JSON object:
246
- {
247
- "nameFilter": "Keywords to search in team names",
248
- "specificTeam": "Specific team name or key if looking for one team",
249
- "myTeams": true/false (true if user wants their teams),
250
- "showAll": true/false (true if user explicitly asks for "all"),
251
- "includeDetails": true/false (true if user wants detailed info)
252
- }
253
-
254
- Only include fields that are clearly mentioned.`;
376
+ Only include fields that are clearly mentioned or implied. Output only the JSON object, with no prose before or after it.`;
255
377
  var searchIssuesTemplate = `Extract search criteria from the user's request for Linear issues.
256
378
 
257
379
  User request: "{{userMessage}}"
@@ -266,53 +388,199 @@ The user might express searches in various ways:
266
388
  - "Bugs that are almost done" → label + state filter
267
389
  - "Show me the oldest open issues" → state + sort order
268
390
 
269
- Extract and return ONLY a JSON object:
391
+ Respond with JSON only. Use this shape:
270
392
  {
271
- "query": "General search text for title/description",
272
- "states": ["state names like In Progress, Done, Todo, Backlog"],
273
- "assignees": ["assignee names or emails, or 'me' for current user"],
274
- "priorities": ["urgent/high/normal/low or 1/2/3/4"],
275
- "teams": ["team names or keys"],
276
- "labels": ["label names"],
277
- "hasAssignee": true/false (true = has assignee, false = unassigned),
393
+ "query": "General search text for title or description",
394
+ "states": ["In Progress"],
395
+ "assignees": ["me"],
396
+ "priorities": ["high"],
397
+ "teams": ["ENG"],
398
+ "labels": ["bug"],
399
+ "hasAssignee": true,
278
400
  "dateRange": {
279
- "field": "created/updated/completed",
280
- "period": "today/yesterday/this-week/last-week/this-month/last-month",
281
- "from": "ISO date if specific date",
282
- "to": "ISO date if specific date"
401
+ "field": "created|updated|completed",
402
+ "period": "today|yesterday|this-week|last-week|this-month|last-month",
403
+ "from": "ISO date if a specific start is mentioned",
404
+ "to": "ISO date if a specific end is mentioned"
283
405
  },
284
406
  "sort": {
285
- "field": "created/updated/priority",
286
- "order": "asc/desc"
407
+ "field": "created|updated|priority",
408
+ "order": "asc|desc"
287
409
  },
288
- "limit": number (default 10)
410
+ "limit": 10
289
411
  }
290
412
 
291
- Only include fields that are clearly mentioned or implied. For "my" issues, set assignees to ["me"].`;
413
+ Only include fields that are clearly mentioned or implied. For "my" issues, set assignees to ["me"]. Output only the JSON object, with no prose before or after it.`;
292
414
  var updateIssueTemplate = `Given the user's request to update a Linear issue, extract the information needed.
293
415
 
294
416
  User request: "{{userMessage}}"
295
417
 
296
- Extract and return ONLY a JSON object (no markdown formatting, no code blocks) with the following structure:
418
+ Respond with JSON only. Use this shape:
297
419
  {
298
- "issueId": "The issue identifier (e.g., ENG-123, COM2-7)",
420
+ "issueId": "Issue identifier such as ENG-123 or COM2-7",
299
421
  "updates": {
300
422
  "title": "New title if changing the title",
301
423
  "description": "New description if changing the description",
302
- "priority": "Priority level if changing (1=urgent, 2=high, 3=normal, 4=low)",
303
- "teamKey": "New team key if moving to another team (e.g., ENG, ELIZA, COM2)",
424
+ "priority": 3,
425
+ "teamKey": "New team key if moving to another team, such as ENG, ELIZA, or COM2",
304
426
  "assignee": "New assignee username or email if changing",
305
- "status": "New status if changing (e.g., todo, in-progress, done, canceled)",
306
- "labels": ["label1", "label2"] (if changing labels, empty array to clear)
427
+ "status": "todo|in-progress|done|canceled",
428
+ "labels": ["label"]
307
429
  }
308
430
  }
309
431
 
310
- Only include fields that are being updated. Return only the JSON object, no other text.`;
432
+ Only include fields that are being updated. Use an empty labels array to clear all labels. Output only the JSON object, with no prose before or after it.`;
433
+
434
+ // src/actions/parseLinearPrompt.ts
435
+ var EMPTY_SCALAR_VALUES = new Set(["", "none", "null", "undefined", "n/a", "not provided"]);
436
+ var EMPTY_LIST_VALUES = new Set([...EMPTY_SCALAR_VALUES, "clear", "clear all", "no labels"]);
437
+ function isRecord(value) {
438
+ return typeof value === "object" && value !== null && !Array.isArray(value);
439
+ }
440
+ function stripWrappingQuotes(value) {
441
+ const trimmed = value.trim();
442
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
443
+ return trimmed.slice(1, -1).trim();
444
+ }
445
+ return trimmed;
446
+ }
447
+ function normalizeListEntry(value) {
448
+ return stripWrappingQuotes(value.replace(/^\s*[-*]\s*/, "").trim());
449
+ }
450
+ function splitListString(value) {
451
+ let trimmed = value.trim();
452
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
453
+ trimmed = trimmed.slice(1, -1);
454
+ }
455
+ return trimmed.split(/[,\n]/).map(normalizeListEntry).filter(Boolean);
456
+ }
457
+ function parseLinearPromptResponse(response) {
458
+ try {
459
+ const trimmed = response.trim();
460
+ const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
461
+ const candidate = (fenced?.[1] ?? trimmed).trim();
462
+ const firstBrace = candidate.indexOf("{");
463
+ const lastBrace = candidate.lastIndexOf("}");
464
+ if (firstBrace < 0 || lastBrace <= firstBrace)
465
+ return {};
466
+ const parsed = JSON.parse(candidate.slice(firstBrace, lastBrace + 1));
467
+ return isRecord(parsed) ? parsed : {};
468
+ } catch {
469
+ return {};
470
+ }
471
+ }
472
+ function getRecordValue(value) {
473
+ if (isRecord(value)) {
474
+ return value;
475
+ }
476
+ if (typeof value === "string" && value.trim().startsWith("{")) {
477
+ const parsed = parseLinearPromptResponse(value);
478
+ return Object.keys(parsed).length > 0 ? parsed : undefined;
479
+ }
480
+ return;
481
+ }
482
+ function getStringValue(value) {
483
+ if (typeof value === "string") {
484
+ const normalized = stripWrappingQuotes(value);
485
+ return EMPTY_SCALAR_VALUES.has(normalized.toLowerCase()) ? undefined : normalized;
486
+ }
487
+ if (typeof value === "number" || typeof value === "boolean") {
488
+ return String(value);
489
+ }
490
+ return;
491
+ }
492
+ function getStringArrayValue(value) {
493
+ if (Array.isArray(value)) {
494
+ return value.flatMap((entry) => {
495
+ if (entry == null) {
496
+ return [];
497
+ }
498
+ if (typeof entry === "string") {
499
+ return splitListString(entry);
500
+ }
501
+ return [String(entry)];
502
+ }).filter(Boolean);
503
+ }
504
+ if (typeof value === "string") {
505
+ const normalized = stripWrappingQuotes(value);
506
+ if (EMPTY_LIST_VALUES.has(normalized.toLowerCase())) {
507
+ return [];
508
+ }
509
+ return splitListString(normalized);
510
+ }
511
+ return;
512
+ }
513
+ function getBooleanValue(value) {
514
+ if (typeof value === "boolean") {
515
+ return value;
516
+ }
517
+ if (typeof value === "string") {
518
+ const normalized = value.trim().toLowerCase();
519
+ if (["true", "yes", "y"].includes(normalized)) {
520
+ return true;
521
+ }
522
+ if (["false", "no", "n"].includes(normalized)) {
523
+ return false;
524
+ }
525
+ }
526
+ return;
527
+ }
528
+ function getNumberValue(value) {
529
+ if (typeof value === "number" && Number.isFinite(value)) {
530
+ return value;
531
+ }
532
+ if (typeof value === "string") {
533
+ const normalized = value.trim();
534
+ if (!normalized) {
535
+ return;
536
+ }
537
+ const parsed = Number(normalized);
538
+ return Number.isFinite(parsed) ? parsed : undefined;
539
+ }
540
+ return;
541
+ }
542
+ function getPriorityNumberValue(value) {
543
+ const numeric = getNumberValue(value);
544
+ if (numeric) {
545
+ return numeric;
546
+ }
547
+ const priority = getStringValue(value)?.toLowerCase();
548
+ if (!priority) {
549
+ return;
550
+ }
551
+ const priorityMap = {
552
+ urgent: 1,
553
+ high: 2,
554
+ normal: 3,
555
+ medium: 3,
556
+ low: 4
557
+ };
558
+ return priorityMap[priority];
559
+ }
311
560
 
312
561
  // src/actions/createComment.ts
313
562
  var createCommentAction = {
314
563
  name: "CREATE_LINEAR_COMMENT",
564
+ contexts: ["tasks", "connectors", "automation"],
565
+ contextGate: { anyOf: ["tasks", "connectors", "automation"] },
566
+ roleGate: { minRole: "USER" },
315
567
  description: "Add a comment to a Linear issue",
568
+ descriptionCompressed: "add comment Linear issue",
569
+ parameters: [
570
+ {
571
+ name: "issueId",
572
+ description: "Linear issue id or identifier to comment on.",
573
+ required: false,
574
+ schema: { type: "string" }
575
+ },
576
+ {
577
+ name: "body",
578
+ description: "Comment body to add to the issue.",
579
+ required: false,
580
+ schema: { type: "string" }
581
+ },
582
+ linearAccountIdParameter
583
+ ],
316
584
  similes: [
317
585
  "create-linear-comment",
318
586
  "add-linear-comment",
@@ -366,37 +634,17 @@ var createCommentAction = {
366
634
  }
367
635
  ]
368
636
  ],
369
- validate: async (runtime, message, state, options) => {
370
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
371
- const __avText = __avTextRaw.toLowerCase();
372
- const __avKeywords = ["create", "linear", "comment"];
373
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
374
- const __avRegex = /\b(?:create|linear|comment)\b/i;
375
- const __avRegexOk = __avRegex.test(__avText);
376
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
377
- const __avExpectedSource = "";
378
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
379
- const __avOptions = options && typeof options === "object" ? options : {};
380
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
381
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
382
- return false;
383
- }
384
- const __avLegacyValidate = async (runtime2, _message, _state) => {
385
- const apiKey = runtime2.getSetting("LINEAR_API_KEY");
386
- return !!apiKey;
387
- };
388
- try {
389
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
390
- } catch {
391
- return false;
392
- }
393
- },
637
+ validate: async (runtime, message, state) => validateLinearActionIntent(runtime, message, state, {
638
+ keywords: ["create", "linear", "comment"],
639
+ regexAlternation: "create|linear|comment"
640
+ }),
394
641
  async handler(runtime, message, _state, _options, callback) {
395
642
  try {
396
643
  const linearService = runtime.getService("linear");
397
644
  if (!linearService) {
398
645
  throw new Error("Linear service not available");
399
646
  }
647
+ const accountId = getLinearAccountId(runtime, _options);
400
648
  const content = message.content.text;
401
649
  if (!content) {
402
650
  const errorMessage = "Please provide a message with the issue and comment content.";
@@ -430,22 +678,28 @@ var createCommentAction = {
430
678
  }
431
679
  } else {
432
680
  try {
433
- const parsed = JSON.parse(response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim());
434
- if (parsed.issueId) {
435
- issueId = parsed.issueId;
436
- commentBody = parsed.commentBody;
437
- } else if (parsed.issueDescription) {
681
+ const parsed = parseLinearPromptResponse(response);
682
+ if (Object.keys(parsed).length === 0) {
683
+ throw new Error("No fields found in model response");
684
+ }
685
+ const parsedIssueId = getStringValue(parsed.issueId);
686
+ const issueDescription = getStringValue(parsed.issueDescription);
687
+ const parsedCommentBody = getStringValue(parsed.commentBody) ?? "";
688
+ if (parsedIssueId) {
689
+ issueId = parsedIssueId;
690
+ commentBody = parsedCommentBody;
691
+ } else if (issueDescription) {
438
692
  const filters = {
439
- query: parsed.issueDescription,
693
+ query: issueDescription,
440
694
  limit: 5
441
695
  };
442
- const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
696
+ const defaultTeamKey = linearService.getDefaultTeamKey(accountId) ?? runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
443
697
  if (defaultTeamKey) {
444
698
  filters.team = defaultTeamKey;
445
699
  }
446
- const issues = await linearService.searchIssues(filters);
700
+ const issues = await linearService.searchIssues(filters, accountId);
447
701
  if (issues.length === 0) {
448
- const errorMessage = `No issues found matching "${parsed.issueDescription}". Please provide a specific issue ID.`;
702
+ const errorMessage = `No issues found matching "${issueDescription}". Please provide a specific issue ID.`;
449
703
  await callback?.({
450
704
  text: errorMessage,
451
705
  source: message.content.source
@@ -457,13 +711,13 @@ var createCommentAction = {
457
711
  }
458
712
  if (issues.length === 1) {
459
713
  issueId = issues[0].identifier;
460
- commentBody = parsed.commentBody;
714
+ commentBody = parsedCommentBody;
461
715
  } else {
462
716
  const issueList = await Promise.all(issues.map(async (issue2, index) => {
463
717
  const state = await issue2.state;
464
718
  return `${index + 1}. ${issue2.identifier}: ${issue2.title} (${state?.name || "No state"})`;
465
719
  }));
466
- const clarifyMessage = `Found multiple issues matching "${parsed.issueDescription}":
720
+ const clarifyMessage = `Found multiple issues matching "${issueDescription}":
467
721
  ${issueList.join(`
468
722
  `)}
469
723
 
@@ -482,15 +736,16 @@ Please specify which issue to comment on by its ID.`;
482
736
  identifier: i.identifier,
483
737
  title: i.title
484
738
  })),
485
- pendingComment: parsed.commentBody
739
+ pendingComment: parsedCommentBody
486
740
  }
487
741
  };
488
742
  }
489
743
  } else {
490
744
  throw new Error("No issue identifier or description found");
491
745
  }
492
- if (parsed.commentType && parsed.commentType !== "note") {
493
- commentBody = `[${parsed.commentType.toUpperCase()}] ${commentBody}`;
746
+ const commentType = getStringValue(parsed.commentType)?.toLowerCase();
747
+ if (commentType && commentType !== "note") {
748
+ commentBody = `[${commentType.toUpperCase()}] ${commentBody}`;
494
749
  }
495
750
  } catch (parseError) {
496
751
  logger2.warn("Failed to parse LLM response, falling back to regex:", parseError);
@@ -522,11 +777,11 @@ Please specify which issue to comment on by its ID.`;
522
777
  success: false
523
778
  };
524
779
  }
525
- const issue = await linearService.getIssue(issueId);
780
+ const issue = await linearService.getIssue(issueId, accountId);
526
781
  const comment = await linearService.createComment({
527
782
  issueId: issue.id,
528
783
  body: commentBody
529
- });
784
+ }, accountId);
530
785
  const successMessage = `✅ Comment added to issue ${issue.identifier}: "${commentBody}"`;
531
786
  await callback?.({
532
787
  text: successMessage,
@@ -540,7 +795,8 @@ Please specify which issue to comment on by its ID.`;
540
795
  issueId: issue.id,
541
796
  issueIdentifier: issue.identifier,
542
797
  commentBody,
543
- createdAt: comment.createdAt instanceof Date ? comment.createdAt.toISOString() : comment.createdAt
798
+ createdAt: comment.createdAt instanceof Date ? comment.createdAt.toISOString() : comment.createdAt,
799
+ accountId
544
800
  }
545
801
  };
546
802
  } catch (error) {
@@ -565,7 +821,30 @@ import {
565
821
  } from "@elizaos/core";
566
822
  var createIssueAction = {
567
823
  name: "CREATE_LINEAR_ISSUE",
568
- description: "Create a new issue in Linear",
824
+ contexts: ["tasks", "connectors", "automation"],
825
+ contextGate: { anyOf: ["tasks", "connectors", "automation"] },
826
+ roleGate: { minRole: "USER" },
827
+ description: "Create a new Linear issue with title, description, priority, team, assignee, and labels. Use when the user wants to file, log, or track a new ticket, bug, story, or task in Linear from chat.",
828
+ descriptionCompressed: "create new issue Linear",
829
+ parameters: [
830
+ {
831
+ name: "issueData",
832
+ description: "Structured Linear issue fields.",
833
+ required: false,
834
+ schema: {
835
+ type: "object",
836
+ properties: {
837
+ title: { type: "string" },
838
+ description: { type: "string" },
839
+ priority: { type: "number" },
840
+ teamId: { type: "string" },
841
+ assigneeId: { type: "string" },
842
+ labelIds: { type: "array", items: { type: "string" } }
843
+ }
844
+ }
845
+ },
846
+ linearAccountIdParameter
847
+ ],
569
848
  similes: ["create-linear-issue", "new-linear-issue", "add-linear-issue"],
570
849
  examples: [
571
850
  [
@@ -599,37 +878,17 @@ var createIssueAction = {
599
878
  }
600
879
  ]
601
880
  ],
602
- validate: async (runtime, message, state, options) => {
603
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
604
- const __avText = __avTextRaw.toLowerCase();
605
- const __avKeywords = ["create", "linear", "issue"];
606
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
607
- const __avRegex = /\b(?:create|linear|issue)\b/i;
608
- const __avRegexOk = __avRegex.test(__avText);
609
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
610
- const __avExpectedSource = "";
611
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
612
- const __avOptions = options && typeof options === "object" ? options : {};
613
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
614
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
615
- return false;
616
- }
617
- const __avLegacyValidate = async (runtime2, _message, _state) => {
618
- const apiKey = runtime2.getSetting("LINEAR_API_KEY");
619
- return !!apiKey;
620
- };
621
- try {
622
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
623
- } catch {
624
- return false;
625
- }
626
- },
881
+ validate: async (runtime, message, state) => validateLinearActionIntent(runtime, message, state, {
882
+ keywords: ["create", "linear", "issue"],
883
+ regexAlternation: "create|linear|issue"
884
+ }),
627
885
  async handler(runtime, message, _state, _options, callback) {
628
886
  try {
629
887
  const linearService = runtime.getService("linear");
630
888
  if (!linearService) {
631
889
  throw new Error("Linear service not available");
632
890
  }
891
+ const accountId = getLinearAccountId(runtime, _options);
633
892
  const content = message.content.text;
634
893
  if (!content) {
635
894
  const errorMessage = "Please provide a description for the issue.";
@@ -656,37 +915,40 @@ var createIssueAction = {
656
915
  throw new Error("Failed to extract issue information");
657
916
  }
658
917
  try {
659
- const cleanedResponse = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
660
- const parsed = JSON.parse(cleanedResponse);
918
+ const parsed = parseLinearPromptResponse(response);
919
+ if (Object.keys(parsed).length === 0) {
920
+ throw new Error("No fields found in model response");
921
+ }
661
922
  issueData = {
662
- title: parsed.title || undefined,
663
- description: parsed.description || undefined,
664
- priority: parsed.priority ? Number(parsed.priority) : undefined
923
+ title: getStringValue(parsed.title),
924
+ description: getStringValue(parsed.description),
925
+ priority: getPriorityNumberValue(parsed.priority)
665
926
  };
666
- if (parsed.teamKey) {
667
- const teams = await linearService.getTeams();
668
- const team = teams.find((t) => t.key.toLowerCase() === parsed.teamKey.toLowerCase());
927
+ const teamKey = getStringValue(parsed.teamKey);
928
+ if (teamKey) {
929
+ const teams = await linearService.getTeams(accountId);
930
+ const team = teams.find((t) => t.key.toLowerCase() === teamKey.toLowerCase());
669
931
  if (team) {
670
932
  issueData.teamId = team.id;
671
933
  }
672
934
  }
673
- if (parsed.assignee && parsed.assignee !== "") {
674
- const cleanAssignee = parsed.assignee.replace(/^@/, "");
675
- const users = await linearService.getUsers();
935
+ const assignee = getStringValue(parsed.assignee);
936
+ if (assignee) {
937
+ const cleanAssignee = assignee.replace(/^@/, "");
938
+ const users = await linearService.getUsers(accountId);
676
939
  const user = users.find((u) => u.email === cleanAssignee || u.name.toLowerCase().includes(cleanAssignee.toLowerCase()));
677
940
  if (user) {
678
941
  issueData.assigneeId = user.id;
679
942
  }
680
943
  }
681
- if (parsed.labels && Array.isArray(parsed.labels) && parsed.labels.length > 0) {
682
- const labels = await linearService.getLabels(issueData.teamId);
944
+ const parsedLabels = getStringArrayValue(parsed.labels);
945
+ if (parsedLabels && parsedLabels.length > 0) {
946
+ const labels = await linearService.getLabels(issueData.teamId, accountId);
683
947
  const labelIds = [];
684
- for (const labelName of parsed.labels) {
685
- if (labelName && labelName !== "") {
686
- const label = labels.find((l) => l.name.toLowerCase() === labelName.toLowerCase());
687
- if (label) {
688
- labelIds.push(label.id);
689
- }
948
+ for (const labelName of parsedLabels) {
949
+ const label = labels.find((l) => l.name.toLowerCase() === labelName.toLowerCase());
950
+ if (label) {
951
+ labelIds.push(label.id);
690
952
  }
691
953
  }
692
954
  if (labelIds.length > 0) {
@@ -694,9 +956,9 @@ var createIssueAction = {
694
956
  }
695
957
  }
696
958
  if (!issueData.teamId) {
697
- const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
959
+ const defaultTeamKey = linearService.getDefaultTeamKey(accountId) ?? runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
698
960
  if (defaultTeamKey) {
699
- const teams = await linearService.getTeams();
961
+ const teams = await linearService.getTeams(accountId);
700
962
  const defaultTeam = teams.find((t) => t.key.toLowerCase() === defaultTeamKey.toLowerCase());
701
963
  if (defaultTeam) {
702
964
  issueData.teamId = defaultTeam.id;
@@ -706,7 +968,7 @@ var createIssueAction = {
706
968
  }
707
969
  }
708
970
  if (!issueData.teamId) {
709
- const teams = await linearService.getTeams();
971
+ const teams = await linearService.getTeams(accountId);
710
972
  if (teams.length > 0) {
711
973
  issueData.teamId = teams[0].id;
712
974
  logger3.warn(`No team specified, using first available team: ${teams[0].name}`);
@@ -719,8 +981,8 @@ var createIssueAction = {
719
981
  title: content.length > 100 ? `${content.substring(0, 100)}...` : content,
720
982
  description: content
721
983
  };
722
- const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
723
- const teams = await linearService.getTeams();
984
+ const defaultTeamKey = linearService.getDefaultTeamKey(accountId) ?? runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
985
+ const teams = await linearService.getTeams(accountId);
724
986
  if (defaultTeamKey) {
725
987
  const defaultTeam = teams.find((t) => t.key.toLowerCase() === defaultTeamKey.toLowerCase());
726
988
  if (defaultTeam) {
@@ -756,7 +1018,7 @@ var createIssueAction = {
756
1018
  success: false
757
1019
  };
758
1020
  }
759
- const issue = await linearService.createIssue(issueData);
1021
+ const issue = await linearService.createIssue(issueData, accountId);
760
1022
  const successMessage = `✅ Created Linear issue: ${issue.title} (${issue.identifier})
761
1023
 
762
1024
  View it at: ${issue.url}`;
@@ -770,7 +1032,8 @@ View it at: ${issue.url}`;
770
1032
  data: {
771
1033
  issueId: issue.id,
772
1034
  identifier: issue.identifier,
773
- url: issue.url
1035
+ url: issue.url,
1036
+ accountId
774
1037
  }
775
1038
  };
776
1039
  } catch (error) {
@@ -788,14 +1051,101 @@ View it at: ${issue.url}`;
788
1051
  }
789
1052
  };
790
1053
 
1054
+ // src/actions/deleteComment.ts
1055
+ import {
1056
+ logger as logger4
1057
+ } from "@elizaos/core";
1058
+ var deleteCommentAction = {
1059
+ name: "DELETE_LINEAR_COMMENT",
1060
+ contexts: ["tasks", "connectors", "automation"],
1061
+ contextGate: { anyOf: ["tasks", "connectors", "automation"] },
1062
+ roleGate: { minRole: "USER" },
1063
+ description: "Delete a specific Linear comment by its comment id. Use when the user asks to remove, retract, or erase a comment they previously left on a Linear issue.",
1064
+ descriptionCompressed: "delete Linear comment id",
1065
+ parameters: [
1066
+ {
1067
+ name: "commentId",
1068
+ description: "Linear comment id to delete.",
1069
+ required: false,
1070
+ schema: { type: "string" }
1071
+ },
1072
+ linearAccountIdParameter
1073
+ ],
1074
+ similes: ["remove-linear-comment", "erase-linear-comment"],
1075
+ examples: [
1076
+ [
1077
+ {
1078
+ name: "User",
1079
+ content: { text: "Delete comment abc-123 from ENG-456." }
1080
+ },
1081
+ {
1082
+ name: "Assistant",
1083
+ content: {
1084
+ text: "I'll delete that comment.",
1085
+ actions: ["DELETE_LINEAR_COMMENT"]
1086
+ }
1087
+ }
1088
+ ]
1089
+ ],
1090
+ validate: async (runtime, message, state) => validateLinearActionIntent(runtime, message, state, {
1091
+ keywords: ["delete", "remove", "linear", "comment"],
1092
+ regexAlternation: "delete|remove|linear|comment"
1093
+ }),
1094
+ async handler(runtime, message, _state, _options, callback) {
1095
+ try {
1096
+ const linearService = runtime.getService("linear");
1097
+ if (!linearService) {
1098
+ throw new Error("Linear service not available");
1099
+ }
1100
+ const accountId = getLinearAccountId(runtime, _options);
1101
+ const params = _options?.parameters;
1102
+ const commentId = params?.commentId?.trim() ?? "";
1103
+ if (!commentId) {
1104
+ const errorMessage = "Please provide a commentId to delete.";
1105
+ await callback?.({ text: errorMessage, source: message.content.source });
1106
+ return { text: errorMessage, success: false };
1107
+ }
1108
+ await linearService.deleteComment(commentId, accountId);
1109
+ const successMessage = `Deleted comment ${commentId}.`;
1110
+ await callback?.({ text: successMessage, source: message.content.source });
1111
+ return {
1112
+ text: successMessage,
1113
+ success: true,
1114
+ data: { commentId, accountId }
1115
+ };
1116
+ } catch (error) {
1117
+ logger4.error("Failed to delete comment:", error);
1118
+ const errorMessage = `Failed to delete comment: ${error instanceof Error ? error.message : "Unknown error"}`;
1119
+ await callback?.({ text: errorMessage, source: message.content.source });
1120
+ return { text: errorMessage, success: false };
1121
+ }
1122
+ }
1123
+ };
1124
+
791
1125
  // src/actions/deleteIssue.ts
792
1126
  import {
793
- logger as logger4,
794
- ModelType as ModelType3
1127
+ logger as logger5,
1128
+ ModelType as ModelType3,
1129
+ requireConfirmation as requireConfirmation2
795
1130
  } from "@elizaos/core";
1131
+ var LINEAR_MODEL_TIMEOUT_MS = 15000;
1132
+ var LINEAR_ISSUE_TITLE_MAX_CHARS = 300;
796
1133
  var deleteIssueAction = {
797
1134
  name: "DELETE_LINEAR_ISSUE",
1135
+ contexts: ["tasks", "connectors", "automation"],
1136
+ contextGate: { anyOf: ["tasks", "connectors", "automation"] },
1137
+ roleGate: { minRole: "USER" },
798
1138
  description: "Delete (archive) an issue in Linear",
1139
+ descriptionCompressed: "delete (archive) issue Linear",
1140
+ parameters: [
1141
+ {
1142
+ name: "issueId",
1143
+ description: "Linear issue id or identifier to archive.",
1144
+ required: false,
1145
+ schema: { type: "string" }
1146
+ },
1147
+ linearAccountIdParameter
1148
+ ],
799
1149
  similes: [
800
1150
  "delete-linear-issue",
801
1151
  "archive-linear-issue",
@@ -849,37 +1199,17 @@ var deleteIssueAction = {
849
1199
  }
850
1200
  ]
851
1201
  ],
852
- validate: async (runtime, message, state, options) => {
853
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
854
- const __avText = __avTextRaw.toLowerCase();
855
- const __avKeywords = ["delete", "linear", "issue"];
856
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
857
- const __avRegex = /\b(?:delete|linear|issue)\b/i;
858
- const __avRegexOk = __avRegex.test(__avText);
859
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
860
- const __avExpectedSource = "";
861
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
862
- const __avOptions = options && typeof options === "object" ? options : {};
863
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
864
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
865
- return false;
866
- }
867
- const __avLegacyValidate = async (runtime2, _message, _state) => {
868
- const apiKey = runtime2.getSetting("LINEAR_API_KEY");
869
- return !!apiKey;
870
- };
871
- try {
872
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
873
- } catch {
874
- return false;
875
- }
876
- },
1202
+ validate: async (runtime, message, state) => validateLinearActionIntent(runtime, message, state, {
1203
+ keywords: ["delete", "linear", "issue"],
1204
+ regexAlternation: "delete|linear|issue"
1205
+ }),
877
1206
  async handler(runtime, message, _state, _options, callback) {
878
1207
  try {
879
1208
  const linearService = runtime.getService("linear");
880
1209
  if (!linearService) {
881
1210
  throw new Error("Linear service not available");
882
1211
  }
1212
+ const accountId = getLinearAccountId(runtime, _options);
883
1213
  const content = message.content.text;
884
1214
  if (!content) {
885
1215
  const errorMessage = "Please specify which issue to delete.";
@@ -898,21 +1228,26 @@ var deleteIssueAction = {
898
1228
  issueId = params.issueId;
899
1229
  } else {
900
1230
  const prompt = deleteIssueTemplate.replace("{{userMessage}}", content);
901
- const response = await runtime.useModel(ModelType3.TEXT_LARGE, {
902
- prompt
903
- });
1231
+ const response = await Promise.race([
1232
+ runtime.useModel(ModelType3.TEXT_LARGE, {
1233
+ prompt
1234
+ }),
1235
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Linear issue extraction timeout")), LINEAR_MODEL_TIMEOUT_MS))
1236
+ ]);
904
1237
  if (!response) {
905
1238
  throw new Error("Failed to extract issue identifier");
906
1239
  }
907
1240
  try {
908
- const cleanedResponse = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
909
- const parsed = JSON.parse(cleanedResponse);
910
- issueId = parsed.issueId;
1241
+ const parsed = parseLinearPromptResponse(response);
1242
+ if (Object.keys(parsed).length === 0) {
1243
+ throw new Error("No fields found in model response");
1244
+ }
1245
+ issueId = getStringValue(parsed.issueId) ?? "";
911
1246
  if (!issueId) {
912
1247
  throw new Error("Issue ID not found in parsed response");
913
1248
  }
914
1249
  } catch (parseError) {
915
- logger4.warn("Failed to parse LLM response, falling back to regex parsing:", parseError);
1250
+ logger5.warn("Failed to parse LLM response, falling back to regex parsing:", parseError);
916
1251
  const issueMatch = content.match(/(\w+-\d+)/);
917
1252
  if (!issueMatch) {
918
1253
  const errorMessage = "Please specify an issue ID (e.g., ENG-123) to delete.";
@@ -928,11 +1263,35 @@ var deleteIssueAction = {
928
1263
  issueId = issueMatch[1];
929
1264
  }
930
1265
  }
931
- const issue = await linearService.getIssue(issueId);
932
- const issueTitle = issue.title;
1266
+ const issue = await linearService.getIssue(issueId, accountId);
1267
+ const issueTitle = issue.title.slice(0, LINEAR_ISSUE_TITLE_MAX_CHARS);
933
1268
  const issueIdentifier = issue.identifier;
934
- logger4.info(`Archiving issue ${issueIdentifier}: ${issueTitle}`);
935
- await linearService.deleteIssue(issueId);
1269
+ const decision = await requireConfirmation2({
1270
+ runtime,
1271
+ message,
1272
+ actionName: "DELETE_LINEAR_ISSUE",
1273
+ pendingKey: `archive:${issue.id}`,
1274
+ prompt: `Archive issue ${issueIdentifier}: "${issueTitle}"? This moves it out of active views. Reply "yes" to confirm.`,
1275
+ callback
1276
+ });
1277
+ if (decision.status === "pending") {
1278
+ return {
1279
+ text: `Awaiting confirmation to archive ${issueIdentifier}.`,
1280
+ success: true,
1281
+ data: { awaitingUserInput: true, issueId: issue.id, identifier: issueIdentifier }
1282
+ };
1283
+ }
1284
+ if (decision.status === "cancelled") {
1285
+ const cancelMessage = `Archive of ${issueIdentifier} cancelled.`;
1286
+ await callback?.({ text: cancelMessage, source: message.content.source });
1287
+ return {
1288
+ text: cancelMessage,
1289
+ success: true,
1290
+ data: { cancelled: true, issueId: issue.id, identifier: issueIdentifier }
1291
+ };
1292
+ }
1293
+ logger5.info(`Archiving issue ${issueIdentifier}: ${issueTitle}`);
1294
+ await linearService.deleteIssue(issueId, accountId);
936
1295
  const successMessage = `✅ Successfully archived issue ${issueIdentifier}: "${issueTitle}"
937
1296
 
938
1297
  The issue has been moved to the archived state and will no longer appear in active views.`;
@@ -947,11 +1306,12 @@ The issue has been moved to the archived state and will no longer appear in acti
947
1306
  issueId: issue.id,
948
1307
  identifier: issueIdentifier,
949
1308
  title: issueTitle,
950
- archived: true
1309
+ archived: true,
1310
+ accountId
951
1311
  }
952
1312
  };
953
1313
  } catch (error) {
954
- logger4.error("Failed to delete issue:", error);
1314
+ logger5.error("Failed to delete issue:", error);
955
1315
  const errorMessage = `❌ Failed to delete issue: ${error instanceof Error ? error.message : "Unknown error"}`;
956
1316
  await callback?.({
957
1317
  text: errorMessage,
@@ -967,18 +1327,49 @@ The issue has been moved to the archived state and will no longer appear in acti
967
1327
 
968
1328
  // src/actions/getActivity.ts
969
1329
  import {
970
- logger as logger5,
1330
+ logger as logger6,
971
1331
  ModelType as ModelType4
972
1332
  } from "@elizaos/core";
1333
+ function formatActivityDetail(value) {
1334
+ if (value === null || value === undefined) {
1335
+ return "none";
1336
+ }
1337
+ if (Array.isArray(value)) {
1338
+ return value.map(formatActivityDetail).join(", ");
1339
+ }
1340
+ if (typeof value === "object") {
1341
+ return Object.entries(value).map(([key, nestedValue]) => `${key}=${formatActivityDetail(nestedValue)}`).join("; ");
1342
+ }
1343
+ return String(value);
1344
+ }
973
1345
  var getActivityAction = {
974
1346
  name: "GET_LINEAR_ACTIVITY",
1347
+ contexts: ["tasks", "connectors", "automation"],
1348
+ contextGate: { anyOf: ["tasks", "connectors", "automation"] },
1349
+ roleGate: { minRole: "USER" },
975
1350
  description: "Get recent Linear activity log with optional filters",
1351
+ descriptionCompressed: "get recent Linear activity log w/ optional filter",
976
1352
  similes: [
977
1353
  "get-linear-activity",
978
1354
  "show-linear-activity",
979
1355
  "view-linear-activity",
980
1356
  "check-linear-activity"
981
1357
  ],
1358
+ parameters: [
1359
+ {
1360
+ name: "filters",
1361
+ description: "Optional activity filters, e.g. fromDate ISO timestamp, action, resource_type, resource_id, or success.",
1362
+ required: false,
1363
+ schema: { type: "object" }
1364
+ },
1365
+ {
1366
+ name: "limit",
1367
+ description: "Maximum number of activity log entries to return.",
1368
+ required: false,
1369
+ schema: { type: "number" }
1370
+ },
1371
+ linearAccountIdParameter
1372
+ ],
982
1373
  examples: [
983
1374
  [
984
1375
  {
@@ -1026,40 +1417,21 @@ var getActivityAction = {
1026
1417
  }
1027
1418
  ]
1028
1419
  ],
1029
- validate: async (runtime, message, state, options) => {
1030
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
1031
- const __avText = __avTextRaw.toLowerCase();
1032
- const __avKeywords = ["get", "linear", "activity"];
1033
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
1034
- const __avRegex = /\b(?:get|linear|activity)\b/i;
1035
- const __avRegexOk = __avRegex.test(__avText);
1036
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
1037
- const __avExpectedSource = "";
1038
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
1039
- const __avOptions = options && typeof options === "object" ? options : {};
1040
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
1041
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
1042
- return false;
1043
- }
1044
- const __avLegacyValidate = async (runtime2, _message, _state) => {
1045
- const apiKey = runtime2.getSetting("LINEAR_API_KEY");
1046
- return !!apiKey;
1047
- };
1048
- try {
1049
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
1050
- } catch {
1051
- return false;
1052
- }
1053
- },
1420
+ validate: async (runtime, message, state) => validateLinearActionIntent(runtime, message, state, {
1421
+ keywords: ["get", "linear", "activity"],
1422
+ regexAlternation: "get|linear|activity"
1423
+ }),
1054
1424
  async handler(runtime, message, _state, _options, callback) {
1055
1425
  try {
1056
1426
  const linearService = runtime.getService("linear");
1057
1427
  if (!linearService) {
1058
1428
  throw new Error("Linear service not available");
1059
1429
  }
1430
+ const accountId = getLinearAccountId(runtime, _options);
1060
1431
  const content = message.content.text || "";
1061
- const filters = {};
1062
- let limit = 10;
1432
+ const params = _options?.parameters ?? {};
1433
+ const filters = { ...params.filters ?? {} };
1434
+ let limit = typeof params.limit === "number" && Number.isFinite(params.limit) ? Math.max(1, Math.floor(params.limit)) : 10;
1063
1435
  if (content) {
1064
1436
  const prompt = getActivityTemplate.replace("{{userMessage}}", content);
1065
1437
  const response = await runtime.useModel(ModelType4.TEXT_LARGE, {
@@ -1067,14 +1439,20 @@ var getActivityAction = {
1067
1439
  });
1068
1440
  if (response) {
1069
1441
  try {
1070
- const parsed = JSON.parse(response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim());
1071
- if (parsed.timeRange) {
1442
+ const parsed = parseLinearPromptResponse(response);
1443
+ if (Object.keys(parsed).length === 0) {
1444
+ throw new Error("No fields found in model response");
1445
+ }
1446
+ const timeRange = getRecordValue(parsed.timeRange);
1447
+ if (timeRange) {
1072
1448
  const now = new Date;
1073
1449
  let fromDate;
1074
- if (parsed.timeRange.from) {
1075
- fromDate = new Date(parsed.timeRange.from);
1076
- } else if (parsed.timeRange.period) {
1077
- switch (parsed.timeRange.period) {
1450
+ const from = getStringValue(timeRange.from);
1451
+ const period = getStringValue(timeRange.period);
1452
+ if (from) {
1453
+ fromDate = new Date(from);
1454
+ } else if (period) {
1455
+ switch (period) {
1078
1456
  case "today":
1079
1457
  fromDate = new Date(now.setHours(0, 0, 0, 0));
1080
1458
  break;
@@ -1099,25 +1477,29 @@ var getActivityAction = {
1099
1477
  filters.fromDate = fromDate.toISOString();
1100
1478
  }
1101
1479
  }
1102
- if (parsed.actionTypes && parsed.actionTypes.length > 0) {
1103
- filters.action = parsed.actionTypes[0];
1480
+ const actionTypes = getStringArrayValue(parsed.actionTypes);
1481
+ if (actionTypes && actionTypes.length > 0) {
1482
+ filters.action = actionTypes[0];
1104
1483
  }
1105
- if (parsed.resourceTypes && parsed.resourceTypes.length > 0) {
1106
- filters.resource_type = parsed.resourceTypes[0];
1484
+ const resourceTypes = getStringArrayValue(parsed.resourceTypes);
1485
+ if (resourceTypes && resourceTypes.length > 0) {
1486
+ filters.resource_type = resourceTypes[0];
1107
1487
  }
1108
- if (parsed.resourceId) {
1109
- filters.resource_id = parsed.resourceId;
1488
+ const resourceId = getStringValue(parsed.resourceId);
1489
+ if (resourceId) {
1490
+ filters.resource_id = resourceId;
1110
1491
  }
1111
- if (parsed.successFilter && parsed.successFilter !== "all") {
1112
- filters.success = parsed.successFilter === "success";
1492
+ const successFilter = getStringValue(parsed.successFilter);
1493
+ if (successFilter && successFilter !== "all") {
1494
+ filters.success = successFilter === "success";
1113
1495
  }
1114
- limit = parsed.limit || 10;
1496
+ limit = getNumberValue(parsed.limit) || 10;
1115
1497
  } catch (parseError) {
1116
- logger5.warn("Failed to parse activity filters:", parseError);
1498
+ logger6.warn("Failed to parse activity filters:", parseError);
1117
1499
  }
1118
1500
  }
1119
1501
  }
1120
- let activity = linearService.getActivityLog(limit * 2, filters);
1502
+ let activity = linearService.getActivityLog(limit * 2, filters, accountId);
1121
1503
  if (filters.fromDate) {
1122
1504
  const fromDateValue = filters.fromDate;
1123
1505
  const fromDate = typeof fromDateValue === "string" ? fromDateValue : fromDateValue instanceof Date ? fromDateValue.toISOString() : String(fromDateValue);
@@ -1137,14 +1519,15 @@ var getActivityAction = {
1137
1519
  text: noActivityMessage,
1138
1520
  success: true,
1139
1521
  data: {
1140
- activity: []
1522
+ activity: [],
1523
+ accountId
1141
1524
  }
1142
1525
  };
1143
1526
  }
1144
1527
  const activityText = activity.map((item, index) => {
1145
1528
  const time = new Date(item.timestamp).toLocaleString();
1146
1529
  const status = item.success ? "✅" : "❌";
1147
- const details = Object.entries(item.details).filter(([key]) => key !== "filters").map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join(", ");
1530
+ const details = Object.entries(item.details).filter(([key]) => key !== "filters").map(([key, value]) => `${key}: ${formatActivityDetail(value)}`).join(", ");
1148
1531
  return `${index + 1}. ${status} ${item.action} on ${item.resource_type} ${item.resource_id}
1149
1532
  Time: ${time}
1150
1533
  ${details ? `Details: ${details}` : ""}${item.error ? `
@@ -1171,18 +1554,19 @@ ${activityText}`;
1171
1554
  resource_id: item.resource_id,
1172
1555
  success: item.success,
1173
1556
  error: item.error,
1174
- details: JSON.stringify(item.details),
1557
+ details: formatActivityDetail(item.details),
1175
1558
  timestamp: typeof item.timestamp === "string" ? item.timestamp : new Date(item.timestamp).toISOString()
1176
1559
  })),
1177
1560
  filters: filters ? {
1178
1561
  ...filters,
1179
1562
  fromDate: filters.fromDate ? typeof filters.fromDate === "string" ? filters.fromDate : String(filters.fromDate) : undefined
1180
1563
  } : undefined,
1181
- count: activity.length
1564
+ count: activity.length,
1565
+ accountId
1182
1566
  }
1183
1567
  };
1184
1568
  } catch (error) {
1185
- logger5.error("Failed to get activity:", error);
1569
+ logger6.error("Failed to get activity:", error);
1186
1570
  const errorMessage = `❌ Failed to get activity: ${error instanceof Error ? error.message : "Unknown error"}`;
1187
1571
  await callback?.({
1188
1572
  text: errorMessage,
@@ -1198,12 +1582,16 @@ ${activityText}`;
1198
1582
 
1199
1583
  // src/actions/getIssue.ts
1200
1584
  import {
1201
- logger as logger6,
1585
+ logger as logger7,
1202
1586
  ModelType as ModelType5
1203
1587
  } from "@elizaos/core";
1204
1588
  var getIssueAction = {
1205
1589
  name: "GET_LINEAR_ISSUE",
1590
+ contexts: ["tasks", "connectors", "knowledge"],
1591
+ contextGate: { anyOf: ["tasks", "connectors", "knowledge"] },
1592
+ roleGate: { minRole: "USER" },
1206
1593
  description: "Get details of a specific Linear issue",
1594
+ descriptionCompressed: "get detail specific Linear issue",
1207
1595
  similes: [
1208
1596
  "get-linear-issue",
1209
1597
  "show-linear-issue",
@@ -1211,6 +1599,21 @@ var getIssueAction = {
1211
1599
  "check-linear-issue",
1212
1600
  "find-linear-issue"
1213
1601
  ],
1602
+ parameters: [
1603
+ {
1604
+ name: "issueId",
1605
+ description: "Linear issue identifier or id, e.g. ENG-123.",
1606
+ required: false,
1607
+ schema: { type: "string" }
1608
+ },
1609
+ {
1610
+ name: "query",
1611
+ description: "Search text when the exact Linear issue identifier is unknown.",
1612
+ required: false,
1613
+ schema: { type: "string" }
1614
+ },
1615
+ linearAccountIdParameter
1616
+ ],
1214
1617
  examples: [
1215
1618
  [
1216
1619
  {
@@ -1258,38 +1661,19 @@ var getIssueAction = {
1258
1661
  }
1259
1662
  ]
1260
1663
  ],
1261
- validate: async (runtime, message, state, options) => {
1262
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
1263
- const __avText = __avTextRaw.toLowerCase();
1264
- const __avKeywords = ["get", "linear", "issue"];
1265
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
1266
- const __avRegex = /\b(?:get|linear|issue)\b/i;
1267
- const __avRegexOk = __avRegex.test(__avText);
1268
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
1269
- const __avExpectedSource = "";
1270
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
1271
- const __avOptions = options && typeof options === "object" ? options : {};
1272
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
1273
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
1274
- return false;
1275
- }
1276
- const __avLegacyValidate = async (runtime2, _message, _state) => {
1277
- const apiKey = runtime2.getSetting("LINEAR_API_KEY");
1278
- return !!apiKey;
1279
- };
1280
- try {
1281
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
1282
- } catch {
1283
- return false;
1284
- }
1285
- },
1664
+ validate: async (runtime, message, state) => validateLinearActionIntent(runtime, message, state, {
1665
+ keywords: ["get", "linear", "issue"],
1666
+ regexAlternation: "get|linear|issue"
1667
+ }),
1286
1668
  async handler(runtime, message, _state, _options, callback) {
1287
1669
  try {
1288
1670
  const linearService = runtime.getService("linear");
1289
1671
  if (!linearService) {
1290
1672
  throw new Error("Linear service not available");
1291
1673
  }
1292
- const content = message.content.text;
1674
+ const accountId = getLinearAccountId(runtime, _options);
1675
+ const params = _options?.parameters ?? {};
1676
+ const content = params.query ?? params.issueId ?? message.content.text;
1293
1677
  if (!content) {
1294
1678
  const errorMessage2 = "Please specify which issue you want to see.";
1295
1679
  await callback?.({
@@ -1301,6 +1685,10 @@ var getIssueAction = {
1301
1685
  success: false
1302
1686
  };
1303
1687
  }
1688
+ if (params.issueId) {
1689
+ const issue = await linearService.getIssue(params.issueId, accountId);
1690
+ return await formatIssueResponse(issue, callback, message);
1691
+ }
1304
1692
  const prompt = getIssueTemplate.replace("{{userMessage}}", content);
1305
1693
  const response = await runtime.useModel(ModelType5.TEXT_LARGE, {
1306
1694
  prompt
@@ -1308,26 +1696,34 @@ var getIssueAction = {
1308
1696
  if (!response) {
1309
1697
  const issueMatch = content.match(/(\w+-\d+)/);
1310
1698
  if (issueMatch) {
1311
- const issue = await linearService.getIssue(issueMatch[1]);
1699
+ const issue = await linearService.getIssue(issueMatch[1], accountId);
1312
1700
  return await formatIssueResponse(issue, callback, message);
1313
1701
  }
1314
1702
  throw new Error("Could not understand issue reference");
1315
1703
  }
1316
1704
  try {
1317
- const parsed = JSON.parse(response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim());
1318
- if (parsed.directId) {
1319
- const issue = await linearService.getIssue(parsed.directId);
1705
+ const parsed = parseLinearPromptResponse(response);
1706
+ if (Object.keys(parsed).length === 0) {
1707
+ throw new Error("No fields found in model response");
1708
+ }
1709
+ const directId = getStringValue(parsed.directId);
1710
+ if (directId) {
1711
+ const issue = await linearService.getIssue(directId, accountId);
1320
1712
  return await formatIssueResponse(issue, callback, message);
1321
1713
  }
1322
- if (parsed.searchBy && Object.keys(parsed.searchBy).length > 0) {
1714
+ const searchBy = getRecordValue(parsed.searchBy);
1715
+ if (searchBy && Object.keys(searchBy).length > 0) {
1323
1716
  const filters = {};
1324
- if (parsed.searchBy.title) {
1325
- filters.query = parsed.searchBy.title;
1717
+ const title = getStringValue(searchBy.title);
1718
+ if (title) {
1719
+ filters.query = title;
1326
1720
  }
1327
- if (parsed.searchBy.assignee) {
1328
- filters.assignee = [parsed.searchBy.assignee];
1721
+ const assignee = getStringValue(searchBy.assignee);
1722
+ if (assignee) {
1723
+ filters.assignee = [assignee];
1329
1724
  }
1330
- if (parsed.searchBy.priority) {
1725
+ const priorityValue = getStringValue(searchBy.priority);
1726
+ if (priorityValue) {
1331
1727
  const priorityMap = {
1332
1728
  urgent: 1,
1333
1729
  high: 2,
@@ -1338,25 +1734,27 @@ var getIssueAction = {
1338
1734
  "3": 3,
1339
1735
  "4": 4
1340
1736
  };
1341
- const priority = priorityMap[parsed.searchBy.priority.toLowerCase()];
1737
+ const priority = priorityMap[priorityValue.toLowerCase()];
1342
1738
  if (priority) {
1343
1739
  filters.priority = [priority];
1344
1740
  }
1345
1741
  }
1346
- if (parsed.searchBy.team) {
1347
- filters.team = parsed.searchBy.team;
1742
+ const team = getStringValue(searchBy.team);
1743
+ if (team) {
1744
+ filters.team = team;
1348
1745
  }
1349
- if (parsed.searchBy.state) {
1350
- filters.state = [parsed.searchBy.state];
1746
+ const state = getStringValue(searchBy.state);
1747
+ if (state) {
1748
+ filters.state = [state];
1351
1749
  }
1352
- const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
1750
+ const defaultTeamKey = linearService.getDefaultTeamKey(accountId) ?? runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
1353
1751
  if (defaultTeamKey && !filters.team) {
1354
1752
  filters.team = defaultTeamKey;
1355
1753
  }
1356
1754
  const issues = await linearService.searchIssues({
1357
1755
  ...filters,
1358
- limit: parsed.searchBy.recency ? 10 : 5
1359
- });
1756
+ limit: getStringValue(searchBy.recency) ? 10 : 5
1757
+ }, accountId);
1360
1758
  if (issues.length === 0) {
1361
1759
  const noResultsMessage = "No issues found matching your criteria.";
1362
1760
  await callback?.({
@@ -1368,18 +1766,18 @@ var getIssueAction = {
1368
1766
  success: false
1369
1767
  };
1370
1768
  }
1371
- if (parsed.searchBy.recency) {
1769
+ if (getStringValue(searchBy.recency)) {
1372
1770
  issues.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
1373
1771
  }
1374
- if (parsed.searchBy.recency && issues.length > 0) {
1772
+ if (getStringValue(searchBy.recency) && issues.length > 0) {
1375
1773
  return await formatIssueResponse(issues[0], callback, message);
1376
1774
  }
1377
1775
  if (issues.length === 1) {
1378
1776
  return await formatIssueResponse(issues[0], callback, message);
1379
1777
  }
1380
1778
  const issueList = await Promise.all(issues.slice(0, 5).map(async (issue, index) => {
1381
- const state = await issue.state;
1382
- return `${index + 1}. ${issue.identifier}: ${issue.title} (${state?.name || "No state"})`;
1779
+ const state2 = await issue.state;
1780
+ return `${index + 1}. ${issue.identifier}: ${issue.title} (${state2?.name || "No state"})`;
1383
1781
  }));
1384
1782
  const clarifyMessage = `Found ${issues.length} issues matching your criteria:
1385
1783
  ${issueList.join(`
@@ -1404,10 +1802,10 @@ Please specify which one you want to see by its ID.`;
1404
1802
  };
1405
1803
  }
1406
1804
  } catch (parseError) {
1407
- logger6.warn("Failed to parse LLM response, falling back to regex:", parseError);
1805
+ logger7.warn("Failed to parse LLM response, falling back to regex:", parseError);
1408
1806
  const issueMatch = content.match(/(\w+-\d+)/);
1409
1807
  if (issueMatch) {
1410
- const issue = await linearService.getIssue(issueMatch[1]);
1808
+ const issue = await linearService.getIssue(issueMatch[1], accountId);
1411
1809
  return await formatIssueResponse(issue, callback, message);
1412
1810
  }
1413
1811
  }
@@ -1421,7 +1819,7 @@ Please specify which one you want to see by its ID.`;
1421
1819
  success: false
1422
1820
  };
1423
1821
  } catch (error) {
1424
- logger6.error("Failed to get issue:", error);
1822
+ logger7.error("Failed to get issue:", error);
1425
1823
  const errorMessage = `❌ Failed to get issue: ${error instanceof Error ? error.message : "Unknown error"}`;
1426
1824
  await callback?.({
1427
1825
  text: errorMessage,
@@ -1512,489 +1910,148 @@ View in Linear: ${issue.url}`;
1512
1910
  };
1513
1911
  }
1514
1912
 
1515
- // src/actions/listProjects.ts
1913
+ // src/actions/listComments.ts
1516
1914
  import {
1517
- logger as logger7,
1518
- ModelType as ModelType6
1915
+ logger as logger8
1519
1916
  } from "@elizaos/core";
1520
- var listProjectsAction = {
1521
- name: "LIST_LINEAR_PROJECTS",
1522
- description: "List projects in Linear with optional filters",
1523
- similes: [
1524
- "list-linear-projects",
1525
- "show-linear-projects",
1526
- "get-linear-projects",
1527
- "view-linear-projects"
1917
+ var listCommentsAction = {
1918
+ name: "LIST_LINEAR_COMMENTS",
1919
+ contexts: ["tasks", "connectors", "automation"],
1920
+ contextGate: { anyOf: ["tasks", "connectors", "automation"] },
1921
+ roleGate: { minRole: "USER" },
1922
+ description: "List comments on a Linear issue",
1923
+ descriptionCompressed: "list comment Linear issue",
1924
+ parameters: [
1925
+ {
1926
+ name: "issueId",
1927
+ description: "Linear issue id or identifier to list comments for.",
1928
+ required: false,
1929
+ schema: { type: "string" }
1930
+ },
1931
+ {
1932
+ name: "limit",
1933
+ description: "Maximum number of comments to return (default 25, max 100).",
1934
+ required: false,
1935
+ schema: { type: "number" }
1936
+ },
1937
+ linearAccountIdParameter
1528
1938
  ],
1939
+ similes: ["get-linear-comments", "show-linear-comments", "fetch-linear-comments"],
1529
1940
  examples: [
1530
1941
  [
1531
1942
  {
1532
1943
  name: "User",
1533
- content: {
1534
- text: "Show me all projects"
1535
- }
1536
- },
1537
- {
1538
- name: "Assistant",
1539
- content: {
1540
- text: "I'll list all the projects in Linear for you.",
1541
- actions: ["LIST_LINEAR_PROJECTS"]
1542
- }
1543
- }
1544
- ],
1545
- [
1546
- {
1547
- name: "User",
1548
- content: {
1549
- text: "What active projects do we have?"
1550
- }
1551
- },
1552
- {
1553
- name: "Assistant",
1554
- content: {
1555
- text: "Let me show you all the active projects.",
1556
- actions: ["LIST_LINEAR_PROJECTS"]
1557
- }
1558
- }
1559
- ],
1560
- [
1561
- {
1562
- name: "User",
1563
- content: {
1564
- text: "Show me projects for the engineering team"
1565
- }
1944
+ content: { text: "Show the comments on ENG-123." }
1566
1945
  },
1567
1946
  {
1568
1947
  name: "Assistant",
1569
1948
  content: {
1570
- text: "I'll find the projects for the engineering team.",
1571
- actions: ["LIST_LINEAR_PROJECTS"]
1949
+ text: "Here are the comments on ENG-123.",
1950
+ actions: ["LIST_LINEAR_COMMENTS"]
1572
1951
  }
1573
1952
  }
1574
1953
  ]
1575
1954
  ],
1576
- validate: async (runtime, message, state, options) => {
1577
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
1578
- const __avText = __avTextRaw.toLowerCase();
1579
- const __avKeywords = ["list", "linear", "projects"];
1580
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
1581
- const __avRegex = /\b(?:list|linear|projects)\b/i;
1582
- const __avRegexOk = __avRegex.test(__avText);
1583
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
1584
- const __avExpectedSource = "";
1585
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
1586
- const __avOptions = options && typeof options === "object" ? options : {};
1587
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
1588
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
1589
- return false;
1590
- }
1591
- const __avLegacyValidate = async (runtime2, _message, _state) => {
1592
- const apiKey = runtime2.getSetting("LINEAR_API_KEY");
1593
- return !!apiKey;
1594
- };
1595
- try {
1596
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
1597
- } catch {
1598
- return false;
1599
- }
1600
- },
1955
+ validate: async (runtime, message, state) => validateLinearActionIntent(runtime, message, state, {
1956
+ keywords: ["list", "comments", "linear", "issue"],
1957
+ regexAlternation: "list|comments|linear|issue"
1958
+ }),
1601
1959
  async handler(runtime, message, _state, _options, callback) {
1602
1960
  try {
1603
1961
  const linearService = runtime.getService("linear");
1604
1962
  if (!linearService) {
1605
1963
  throw new Error("Linear service not available");
1606
1964
  }
1607
- const content = message.content.text || "";
1608
- let teamId;
1609
- let showAll = false;
1610
- let stateFilter;
1611
- if (content) {
1612
- const prompt = listProjectsTemplate.replace("{{userMessage}}", content);
1613
- const response = await runtime.useModel(ModelType6.TEXT_LARGE, {
1614
- prompt
1615
- });
1616
- if (response) {
1617
- try {
1618
- const parsed = JSON.parse(response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim());
1619
- if (parsed.teamFilter) {
1620
- const teams = await linearService.getTeams();
1621
- const team = teams.find((t) => t.key.toLowerCase() === parsed.teamFilter.toLowerCase() || t.name.toLowerCase() === parsed.teamFilter.toLowerCase());
1622
- if (team) {
1623
- teamId = team.id;
1624
- logger7.info(`Filtering projects by team: ${team.name} (${team.key})`);
1625
- }
1626
- }
1627
- showAll = parsed.showAll === true;
1628
- stateFilter = parsed.stateFilter;
1629
- } catch (parseError) {
1630
- logger7.warn("Failed to parse project filters, using basic parsing:", parseError);
1631
- const teamMatch = content.match(/(?:for|in|of)\s+(?:the\s+)?(\w+)\s+team/i);
1632
- if (teamMatch) {
1633
- const teams = await linearService.getTeams();
1634
- const team = teams.find((t) => t.key.toLowerCase() === teamMatch[1].toLowerCase() || t.name.toLowerCase() === teamMatch[1].toLowerCase());
1635
- if (team) {
1636
- teamId = team.id;
1637
- logger7.info(`Filtering projects by team: ${team.name} (${team.key})`);
1638
- }
1639
- }
1640
- showAll = content.toLowerCase().includes("all") && content.toLowerCase().includes("project");
1641
- }
1642
- }
1643
- }
1644
- if (!teamId && !showAll) {
1645
- const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
1646
- if (defaultTeamKey) {
1647
- const teams = await linearService.getTeams();
1648
- const defaultTeam = teams.find((t) => t.key.toLowerCase() === defaultTeamKey.toLowerCase());
1649
- if (defaultTeam) {
1650
- teamId = defaultTeam.id;
1651
- logger7.info(`Applying default team filter for projects: ${defaultTeam.name} (${defaultTeam.key})`);
1652
- }
1965
+ const accountId = getLinearAccountId(runtime, _options);
1966
+ const params = _options?.parameters;
1967
+ const issueId = params?.issueId?.trim() ?? "";
1968
+ const limit = typeof params?.limit === "number" ? Math.min(Math.max(1, params.limit), 100) : 25;
1969
+ if (!issueId) {
1970
+ const match = message.content.text?.match(/([A-Z]+-\d+)/);
1971
+ if (!match) {
1972
+ const errorMessage = "Please provide an issueId to list comments for.";
1973
+ await callback?.({
1974
+ text: errorMessage,
1975
+ source: message.content.source
1976
+ });
1977
+ return { text: errorMessage, success: false };
1653
1978
  }
1979
+ const extractedId = match[1];
1980
+ const comments2 = await linearService.listComments(extractedId, limit, accountId);
1981
+ return formatCommentResult(extractedId, comments2, message, callback);
1654
1982
  }
1655
- let projects = await linearService.getProjects(teamId);
1656
- if (stateFilter && stateFilter !== "all") {
1657
- projects = projects.filter((project) => {
1658
- const state = project.state?.toLowerCase() || "";
1659
- if (stateFilter === "active") {
1660
- return state === "started" || state === "in progress" || !state;
1661
- } else if (stateFilter === "planned") {
1662
- return state === "planned" || state === "backlog";
1663
- } else if (stateFilter === "completed") {
1664
- return state === "completed" || state === "done" || state === "canceled";
1665
- }
1666
- return true;
1667
- });
1668
- }
1669
- if (projects.length === 0) {
1670
- const noProjectsMessage = teamId ? "No projects found for the specified team." : "No projects found in Linear.";
1671
- await callback?.({
1672
- text: noProjectsMessage,
1673
- source: message.content.source
1674
- });
1675
- return {
1676
- text: noProjectsMessage,
1677
- success: true,
1678
- data: {
1679
- projects: []
1680
- }
1681
- };
1682
- }
1683
- const projectsWithDetails = await Promise.all(projects.map(async (project) => {
1684
- const teamsQuery = await project.teams();
1685
- const teams = await teamsQuery.nodes;
1686
- const lead = await project.lead;
1687
- return {
1688
- ...project,
1689
- teamsList: teams,
1690
- leadUser: lead
1691
- };
1692
- }));
1693
- const projectList = projectsWithDetails.map((project, index) => {
1694
- const teamNames = project.teamsList.map((t) => t.name).join(", ") || "No teams";
1695
- const status = project.state || "Active";
1696
- const progress = project.progress ? ` (${Math.round(project.progress * 100)}% complete)` : "";
1697
- const lead = project.leadUser ? ` | Lead: ${project.leadUser.name}` : "";
1698
- const dates = [];
1699
- if (project.startDate)
1700
- dates.push(`Start: ${new Date(project.startDate).toLocaleDateString()}`);
1701
- if (project.targetDate)
1702
- dates.push(`Due: ${new Date(project.targetDate).toLocaleDateString()}`);
1703
- const dateInfo = dates.length > 0 ? `
1704
- ${dates.join(" | ")}` : "";
1705
- return `${index + 1}. ${project.name}${project.description ? ` - ${project.description}` : ""}
1706
- Status: ${status}${progress} | Teams: ${teamNames}${lead}${dateInfo}`;
1707
- }).join(`
1708
-
1709
- `);
1710
- const headerText = stateFilter && stateFilter !== "all" ? `\uD83D\uDCC1 Found ${projects.length} ${stateFilter} project${projects.length === 1 ? "" : "s"}:` : `\uD83D\uDCC1 Found ${projects.length} project${projects.length === 1 ? "" : "s"}:`;
1711
- const resultMessage = `${headerText}
1712
-
1713
- ${projectList}`;
1714
- await callback?.({
1715
- text: resultMessage,
1716
- source: message.content.source
1717
- });
1718
- return {
1719
- text: `Found ${projects.length} project${projects.length === 1 ? "" : "s"}`,
1720
- success: true,
1721
- data: {
1722
- projects: projectsWithDetails.map((p) => ({
1723
- id: p.id,
1724
- name: p.name,
1725
- description: p.description,
1726
- url: p.url,
1727
- teams: p.teamsList.map((t) => ({
1728
- id: t.id,
1729
- name: t.name,
1730
- key: t.key
1731
- })),
1732
- lead: p.leadUser ? {
1733
- id: p.leadUser.id,
1734
- name: p.leadUser.name,
1735
- email: p.leadUser.email
1736
- } : null,
1737
- state: p.state,
1738
- progress: p.progress,
1739
- startDate: p.startDate,
1740
- targetDate: p.targetDate
1741
- })),
1742
- count: projects.length,
1743
- filters: {
1744
- team: teamId,
1745
- state: stateFilter
1746
- }
1747
- }
1748
- };
1983
+ const comments = await linearService.listComments(issueId, limit, accountId);
1984
+ return formatCommentResult(issueId, comments, message, callback);
1749
1985
  } catch (error) {
1750
- logger7.error("Failed to list projects:", error);
1751
- const errorMessage = `❌ Failed to list projects: ${error instanceof Error ? error.message : "Unknown error"}`;
1752
- await callback?.({
1753
- text: errorMessage,
1754
- source: message.content.source
1755
- });
1756
- return {
1757
- text: errorMessage,
1758
- success: false
1759
- };
1986
+ logger8.error("Failed to list comments:", error);
1987
+ const errorMessage = `Failed to list comments: ${error instanceof Error ? error.message : "Unknown error"}`;
1988
+ await callback?.({ text: errorMessage, source: message.content.source });
1989
+ return { text: errorMessage, success: false };
1760
1990
  }
1761
1991
  }
1762
1992
  };
1763
-
1764
- // src/actions/listTeams.ts
1765
- import {
1766
- logger as logger8,
1767
- ModelType as ModelType7
1768
- } from "@elizaos/core";
1769
- var listTeamsAction = {
1770
- name: "LIST_LINEAR_TEAMS",
1771
- description: "List teams in Linear with optional filters",
1772
- similes: ["list-linear-teams", "show-linear-teams", "get-linear-teams", "view-linear-teams"],
1773
- examples: [
1774
- [
1775
- {
1776
- name: "User",
1777
- content: {
1778
- text: "Show me all teams"
1779
- }
1780
- },
1781
- {
1782
- name: "Assistant",
1783
- content: {
1784
- text: "I'll list all the teams in Linear for you.",
1785
- actions: ["LIST_LINEAR_TEAMS"]
1786
- }
1787
- }
1788
- ],
1789
- [
1790
- {
1791
- name: "User",
1792
- content: {
1793
- text: "Which engineering teams do we have?"
1794
- }
1795
- },
1796
- {
1797
- name: "Assistant",
1798
- content: {
1799
- text: "Let me find the engineering teams for you.",
1800
- actions: ["LIST_LINEAR_TEAMS"]
1801
- }
1802
- }
1803
- ],
1804
- [
1805
- {
1806
- name: "User",
1807
- content: {
1808
- text: "Show me the teams I'm part of"
1809
- }
1810
- },
1811
- {
1812
- name: "Assistant",
1813
- content: {
1814
- text: "I'll show you the teams you're a member of.",
1815
- actions: ["LIST_LINEAR_TEAMS"]
1816
- }
1817
- }
1818
- ]
1819
- ],
1820
- validate: async (runtime, message, state, options) => {
1821
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
1822
- const __avText = __avTextRaw.toLowerCase();
1823
- const __avKeywords = ["list", "linear", "teams"];
1824
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
1825
- const __avRegex = /\b(?:list|linear|teams)\b/i;
1826
- const __avRegexOk = __avRegex.test(__avText);
1827
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
1828
- const __avExpectedSource = "";
1829
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
1830
- const __avOptions = options && typeof options === "object" ? options : {};
1831
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
1832
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
1833
- return false;
1834
- }
1835
- const __avLegacyValidate = async (runtime2, _message, _state) => {
1836
- const apiKey = runtime2.getSetting("LINEAR_API_KEY");
1837
- return !!apiKey;
1838
- };
1839
- try {
1840
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
1841
- } catch {
1842
- return false;
1843
- }
1844
- },
1845
- async handler(runtime, message, _state, _options, callback) {
1846
- try {
1847
- const linearService = runtime.getService("linear");
1848
- if (!linearService) {
1849
- throw new Error("Linear service not available");
1850
- }
1851
- const content = message.content.text || "";
1852
- let nameFilter;
1853
- let specificTeam;
1854
- let myTeams = false;
1855
- let includeDetails = false;
1856
- if (content) {
1857
- const prompt = listTeamsTemplate.replace("{{userMessage}}", content);
1858
- const response = await runtime.useModel(ModelType7.TEXT_LARGE, {
1859
- prompt
1860
- });
1861
- if (response) {
1862
- try {
1863
- const parsed = JSON.parse(response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim());
1864
- nameFilter = parsed.nameFilter;
1865
- specificTeam = parsed.specificTeam;
1866
- myTeams = parsed.myTeams === true;
1867
- includeDetails = parsed.includeDetails === true;
1868
- } catch (parseError) {
1869
- logger8.warn("Failed to parse team filters:", parseError);
1870
- }
1871
- }
1872
- }
1873
- let teams = await linearService.getTeams();
1874
- if (specificTeam) {
1875
- teams = teams.filter((team) => team.key.toLowerCase() === specificTeam.toLowerCase() || team.name.toLowerCase() === specificTeam.toLowerCase());
1876
- }
1877
- if (nameFilter && !specificTeam) {
1878
- const keywords = nameFilter.toLowerCase().split(/\s+/);
1879
- teams = teams.filter((team) => {
1880
- const teamText = `${team.name} ${team.description || ""}`.toLowerCase();
1881
- return keywords.some((keyword) => teamText.includes(keyword));
1882
- });
1883
- }
1884
- if (myTeams) {
1885
- try {
1886
- const userTeams = await linearService.getUserTeams();
1887
- const userTeamIds = new Set(userTeams.map((t) => t.id));
1888
- teams = teams.filter((team) => userTeamIds.has(team.id));
1889
- } catch (error) {
1890
- logger8.warn("Could not filter for user's teams:", error);
1891
- }
1892
- }
1893
- if (teams.length === 0) {
1894
- const noTeamsMessage = specificTeam ? `No team found matching "${specificTeam}".` : nameFilter ? `No teams found matching "${nameFilter}".` : "No teams found in Linear.";
1895
- await callback?.({
1896
- text: noTeamsMessage,
1897
- source: message.content.source
1898
- });
1899
- return {
1900
- text: noTeamsMessage,
1901
- success: true,
1902
- data: {
1903
- teams: []
1904
- }
1905
- };
1906
- }
1907
- let teamsWithDetails = teams;
1908
- if (includeDetails || specificTeam) {
1909
- teamsWithDetails = await Promise.all(teams.map(async (team) => {
1910
- const membersQuery = await team.members();
1911
- const members = await membersQuery.nodes;
1912
- const projectsQuery = await team.projects();
1913
- const projects = await projectsQuery.nodes;
1914
- return Object.assign(team, {
1915
- memberCount: members.length,
1916
- projectCount: projects.length,
1917
- membersList: specificTeam ? members.slice(0, 5) : []
1918
- });
1919
- }));
1920
- }
1921
- const teamList = teamsWithDetails.map((team, index) => {
1922
- let info = `${index + 1}. ${team.name} (${team.key})`;
1923
- if (team.description) {
1924
- info += `
1925
- ${team.description}`;
1926
- }
1927
- if (includeDetails || specificTeam) {
1928
- info += `
1929
- Members: ${team.memberCount ?? 0} | Projects: ${team.projectCount ?? 0}`;
1930
- const membersList = team.membersList ?? [];
1931
- if (specificTeam && membersList.length > 0) {
1932
- const memberNames = membersList.map((m) => m.name).join(", ");
1933
- info += `
1934
- Team members: ${memberNames}${(team.memberCount ?? 0) > 5 ? " ..." : ""}`;
1935
- }
1936
- }
1937
- return info;
1938
- }).join(`
1939
-
1940
- `);
1941
- const headerText = specificTeam && teams.length === 1 ? `\uD83D\uDCCB Team Details:` : nameFilter ? `\uD83D\uDCCB Found ${teams.length} team${teams.length === 1 ? "" : "s"} matching "${nameFilter}":` : `\uD83D\uDCCB Found ${teams.length} team${teams.length === 1 ? "" : "s"}:`;
1942
- const resultMessage = `${headerText}
1943
-
1944
- ${teamList}`;
1945
- await callback?.({
1946
- text: resultMessage,
1947
- source: message.content.source
1948
- });
1949
- return {
1950
- text: `Found ${teams.length} team${teams.length === 1 ? "" : "s"}`,
1951
- success: true,
1952
- data: {
1953
- teams: teamsWithDetails.map((t) => ({
1954
- id: t.id,
1955
- name: t.name,
1956
- key: t.key,
1957
- description: t.description,
1958
- memberCount: t.memberCount,
1959
- projectCount: t.projectCount
1960
- })),
1961
- count: teams.length,
1962
- filters: {
1963
- name: nameFilter,
1964
- specific: specificTeam
1965
- }
1966
- }
1967
- };
1968
- } catch (error) {
1969
- logger8.error("Failed to list teams:", error);
1970
- const errorMessage = `❌ Failed to list teams: ${error instanceof Error ? error.message : "Unknown error"}`;
1971
- await callback?.({
1972
- text: errorMessage,
1973
- source: message.content.source
1974
- });
1975
- return {
1976
- text: errorMessage,
1977
- success: false
1978
- };
1979
- }
1993
+ async function formatCommentResult(issueId, comments, message, callback) {
1994
+ if (comments.length === 0) {
1995
+ const text2 = `No comments on issue ${issueId}.`;
1996
+ await callback?.({ text: text2, source: message.content.source });
1997
+ return { text: text2, success: true, data: { issueId, comments: [] } };
1980
1998
  }
1981
- };
1999
+ const lines = await Promise.all(comments.map(async (c) => {
2000
+ const user = await c.user;
2001
+ const name = user?.name ?? "unknown";
2002
+ const created = c.createdAt ? new Date(c.createdAt).toISOString().slice(0, 10) : "?";
2003
+ const body = (c.body ?? "").slice(0, 200);
2004
+ return `- [${c.id}] ${name} (${created}): ${body}`;
2005
+ }));
2006
+ const text = `${comments.length} comment(s) on ${issueId}:
2007
+ ${lines.join(`
2008
+ `)}`;
2009
+ await callback?.({ text, source: message.content.source });
2010
+ return {
2011
+ text,
2012
+ success: true,
2013
+ data: {
2014
+ issueId,
2015
+ count: comments.length,
2016
+ comments: comments.map((c) => ({ id: c.id }))
2017
+ }
2018
+ };
2019
+ }
1982
2020
 
1983
2021
  // src/actions/searchIssues.ts
1984
2022
  import {
1985
2023
  logger as logger9,
1986
- ModelType as ModelType8
2024
+ ModelType as ModelType6
1987
2025
  } from "@elizaos/core";
1988
2026
  var searchTemplate = searchIssuesTemplate;
1989
2027
  var searchIssuesAction = {
1990
2028
  name: "SEARCH_LINEAR_ISSUES",
2029
+ contexts: ["tasks", "connectors", "knowledge"],
2030
+ contextGate: { anyOf: ["tasks", "connectors", "knowledge"] },
2031
+ roleGate: { minRole: "USER" },
1991
2032
  description: "Search for issues in Linear with various filters",
2033
+ descriptionCompressed: "search issue Linear w/ various filter",
1992
2034
  similes: [
1993
2035
  "search-linear-issues",
1994
2036
  "find-linear-issues",
1995
2037
  "query-linear-issues",
1996
2038
  "list-linear-issues"
1997
2039
  ],
2040
+ parameters: [
2041
+ {
2042
+ name: "filters",
2043
+ description: "Structured Linear issue filters: query, state, assignee, priority, team, label, and limit.",
2044
+ required: false,
2045
+ schema: { type: "object" }
2046
+ },
2047
+ {
2048
+ name: "limit",
2049
+ description: "Maximum number of issues to return.",
2050
+ required: false,
2051
+ schema: { type: "number" }
2052
+ },
2053
+ linearAccountIdParameter
2054
+ ],
1998
2055
  examples: [
1999
2056
  [
2000
2057
  {
@@ -2042,37 +2099,17 @@ var searchIssuesAction = {
2042
2099
  }
2043
2100
  ]
2044
2101
  ],
2045
- validate: async (runtime, message, state, options) => {
2046
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
2047
- const __avText = __avTextRaw.toLowerCase();
2048
- const __avKeywords = ["search", "linear", "issues"];
2049
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
2050
- const __avRegex = /\b(?:search|linear|issues)\b/i;
2051
- const __avRegexOk = __avRegex.test(__avText);
2052
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
2053
- const __avExpectedSource = "";
2054
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
2055
- const __avOptions = options && typeof options === "object" ? options : {};
2056
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
2057
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
2058
- return false;
2059
- }
2060
- const __avLegacyValidate = async (runtime2, _message, _state) => {
2061
- const apiKey = runtime2.getSetting("LINEAR_API_KEY");
2062
- return !!apiKey;
2063
- };
2064
- try {
2065
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
2066
- } catch {
2067
- return false;
2068
- }
2069
- },
2102
+ validate: async (runtime, message, state) => validateLinearActionIntent(runtime, message, state, {
2103
+ keywords: ["search", "linear", "issues"],
2104
+ regexAlternation: "search|linear|issues"
2105
+ }),
2070
2106
  async handler(runtime, message, _state, _options, callback) {
2071
2107
  try {
2072
2108
  const linearService = runtime.getService("linear");
2073
2109
  if (!linearService) {
2074
2110
  throw new Error("Linear service not available");
2075
2111
  }
2112
+ const accountId = getLinearAccountId(runtime, _options);
2076
2113
  const content = message.content.text;
2077
2114
  if (!content) {
2078
2115
  const errorMessage = "Please provide search criteria for issues.";
@@ -2091,28 +2128,32 @@ var searchIssuesAction = {
2091
2128
  filters = params.filters;
2092
2129
  } else {
2093
2130
  const prompt = searchTemplate.replace("{{userMessage}}", content);
2094
- const response = await runtime.useModel(ModelType8.TEXT_LARGE, {
2131
+ const response = await runtime.useModel(ModelType6.TEXT_LARGE, {
2095
2132
  prompt
2096
2133
  });
2097
2134
  if (!response) {
2098
2135
  filters = { query: content };
2099
2136
  } else {
2100
2137
  try {
2101
- const cleanedResponse = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
2102
- const parsed = JSON.parse(cleanedResponse);
2138
+ const parsed = parseLinearPromptResponse(response);
2139
+ if (Object.keys(parsed).length === 0) {
2140
+ throw new Error("No fields found in model response");
2141
+ }
2103
2142
  filters = {
2104
- query: parsed.query,
2105
- limit: parsed.limit || 10
2143
+ query: getStringValue(parsed.query),
2144
+ limit: getNumberValue(parsed.limit) || 10
2106
2145
  };
2107
- if (parsed.states && parsed.states.length > 0) {
2108
- filters.state = parsed.states;
2146
+ const states = getStringArrayValue(parsed.states);
2147
+ if (states && states.length > 0) {
2148
+ filters.state = states;
2109
2149
  }
2110
- if (parsed.assignees && parsed.assignees.length > 0) {
2150
+ const assignees = getStringArrayValue(parsed.assignees);
2151
+ if (assignees && assignees.length > 0) {
2111
2152
  const processedAssignees = [];
2112
- for (const assignee of parsed.assignees) {
2153
+ for (const assignee of assignees) {
2113
2154
  if (assignee.toLowerCase() === "me") {
2114
2155
  try {
2115
- const currentUser = await linearService.getCurrentUser();
2156
+ const currentUser = await linearService.getCurrentUser(accountId);
2116
2157
  processedAssignees.push(currentUser.email);
2117
2158
  } catch {
2118
2159
  logger9.warn('Could not resolve "me" to current user');
@@ -2125,10 +2166,11 @@ var searchIssuesAction = {
2125
2166
  filters.assignee = processedAssignees;
2126
2167
  }
2127
2168
  }
2128
- if (parsed.hasAssignee === false) {
2169
+ if (getBooleanValue(parsed.hasAssignee) === false) {
2129
2170
  filters.query = filters.query ? `${filters.query} unassigned` : "unassigned";
2130
2171
  }
2131
- if (parsed.priorities && parsed.priorities.length > 0) {
2172
+ const parsedPriorities = getStringArrayValue(parsed.priorities);
2173
+ if (parsedPriorities && parsedPriorities.length > 0) {
2132
2174
  const priorityMap = {
2133
2175
  urgent: 1,
2134
2176
  high: 2,
@@ -2139,16 +2181,18 @@ var searchIssuesAction = {
2139
2181
  "3": 3,
2140
2182
  "4": 4
2141
2183
  };
2142
- const priorities = parsed.priorities.map((p) => priorityMap[p.toLowerCase()]).filter(Boolean);
2184
+ const priorities = parsedPriorities.map((p) => priorityMap[p.toLowerCase()]).filter(Boolean);
2143
2185
  if (priorities.length > 0) {
2144
2186
  filters.priority = priorities;
2145
2187
  }
2146
2188
  }
2147
- if (parsed.teams && parsed.teams.length > 0) {
2148
- filters.team = parsed.teams[0];
2189
+ const teams = getStringArrayValue(parsed.teams);
2190
+ if (teams && teams.length > 0) {
2191
+ filters.team = teams[0];
2149
2192
  }
2150
- if (parsed.labels && parsed.labels.length > 0) {
2151
- filters.label = parsed.labels;
2193
+ const labels = getStringArrayValue(parsed.labels);
2194
+ if (labels && labels.length > 0) {
2195
+ filters.label = labels;
2152
2196
  }
2153
2197
  Object.keys(filters).forEach((key) => {
2154
2198
  if (filters[key] === undefined) {
@@ -2162,7 +2206,7 @@ var searchIssuesAction = {
2162
2206
  }
2163
2207
  }
2164
2208
  if (!filters.team) {
2165
- const defaultTeamKey = runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
2209
+ const defaultTeamKey = linearService.getDefaultTeamKey(accountId) ?? runtime.getSetting("LINEAR_DEFAULT_TEAM_KEY");
2166
2210
  if (defaultTeamKey) {
2167
2211
  const searchingAllIssues = content.toLowerCase().includes("all") && (content.toLowerCase().includes("issue") || content.toLowerCase().includes("bug") || content.toLowerCase().includes("task"));
2168
2212
  if (!searchingAllIssues) {
@@ -2172,7 +2216,7 @@ var searchIssuesAction = {
2172
2216
  }
2173
2217
  }
2174
2218
  filters.limit = params?.limit ?? filters.limit ?? 10;
2175
- const issues = await linearService.searchIssues(filters);
2219
+ const issues = await linearService.searchIssues(filters, accountId);
2176
2220
  if (issues.length === 0) {
2177
2221
  const noResultsMessage = "No issues found matching your search criteria.";
2178
2222
  await callback?.({
@@ -2185,7 +2229,8 @@ var searchIssuesAction = {
2185
2229
  data: {
2186
2230
  issues: [],
2187
2231
  filters: filters ? { ...filters } : undefined,
2188
- count: 0
2232
+ count: 0,
2233
+ accountId
2189
2234
  }
2190
2235
  };
2191
2236
  }
@@ -2229,7 +2274,8 @@ ${issueText}`;
2229
2274
  };
2230
2275
  })),
2231
2276
  filters: filters ? { ...filters } : undefined,
2232
- count: issues.length
2277
+ count: issues.length,
2278
+ accountId
2233
2279
  }
2234
2280
  };
2235
2281
  } catch (error) {
@@ -2247,223 +2293,159 @@ ${issueText}`;
2247
2293
  }
2248
2294
  };
2249
2295
 
2250
- // src/actions/updateIssue.ts
2296
+ // src/actions/updateComment.ts
2251
2297
  import {
2252
- logger as logger10,
2253
- ModelType as ModelType9
2298
+ logger as logger10
2254
2299
  } from "@elizaos/core";
2255
- var updateIssueAction = {
2256
- name: "UPDATE_LINEAR_ISSUE",
2257
- description: "Update an existing Linear issue",
2258
- similes: [
2259
- "update-linear-issue",
2260
- "edit-linear-issue",
2261
- "modify-linear-issue",
2262
- "move-linear-issue",
2263
- "change-linear-issue"
2264
- ],
2265
- examples: [
2266
- [
2267
- {
2268
- name: "User",
2269
- content: {
2270
- text: 'Update issue ENG-123 title to "Fix login button on all devices"'
2271
- }
2272
- },
2273
- {
2274
- name: "Assistant",
2275
- content: {
2276
- text: "I'll update the title of issue ENG-123 for you.",
2277
- actions: ["UPDATE_LINEAR_ISSUE"]
2278
- }
2279
- }
2280
- ],
2281
- [
2282
- {
2283
- name: "User",
2284
- content: {
2285
- text: "Move issue COM2-7 to the ELIZA team"
2286
- }
2287
- },
2288
- {
2289
- name: "Assistant",
2290
- content: {
2291
- text: "I'll move issue COM2-7 to the ELIZA team.",
2292
- actions: ["UPDATE_LINEAR_ISSUE"]
2293
- }
2294
- }
2295
- ],
2296
- [
2297
- {
2298
- name: "User",
2299
- content: {
2300
- text: "Change the priority of BUG-456 to high and assign to john@example.com"
2301
- }
2302
- },
2303
- {
2304
- name: "Assistant",
2305
- content: {
2306
- text: "I'll change the priority of BUG-456 to high and assign it to john@example.com.",
2307
- actions: ["UPDATE_LINEAR_ISSUE"]
2308
- }
2309
- }
2310
- ]
2311
- ],
2312
- validate: async (runtime, message, state, options) => {
2313
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
2314
- const __avText = __avTextRaw.toLowerCase();
2315
- const __avKeywords = ["update", "linear", "issue"];
2316
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
2317
- const __avRegex = /\b(?:update|linear|issue)\b/i;
2318
- const __avRegexOk = __avRegex.test(__avText);
2319
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
2320
- const __avExpectedSource = "";
2321
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
2322
- const __avOptions = options && typeof options === "object" ? options : {};
2323
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
2324
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
2325
- return false;
2300
+ async function handleUpdateComment(runtime, message, _state, _options, callback) {
2301
+ try {
2302
+ const linearService = runtime.getService("linear");
2303
+ if (!linearService) {
2304
+ throw new Error("Linear service not available");
2305
+ }
2306
+ const accountId = getLinearAccountId(runtime, _options);
2307
+ const params = _options?.parameters;
2308
+ const commentId = params?.commentId?.trim() ?? "";
2309
+ const body = params?.body?.trim() ?? "";
2310
+ if (!commentId || !body) {
2311
+ const errorMessage = "Please provide both commentId and body to update a comment.";
2312
+ await callback?.({ text: errorMessage, source: message.content.source });
2313
+ return { text: errorMessage, success: false };
2326
2314
  }
2327
- const __avLegacyValidate = async (runtime2, _message, _state) => {
2328
- const apiKey = runtime2.getSetting("LINEAR_API_KEY");
2329
- return !!apiKey;
2315
+ const comment = await linearService.updateComment(commentId, body, accountId);
2316
+ const successMessage = `Updated comment ${commentId}.`;
2317
+ await callback?.({ text: successMessage, source: message.content.source });
2318
+ return {
2319
+ text: successMessage,
2320
+ success: true,
2321
+ data: { commentId: comment.id, accountId }
2330
2322
  };
2331
- try {
2332
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
2333
- } catch {
2334
- return false;
2323
+ } catch (error) {
2324
+ logger10.error("Failed to update comment:", error);
2325
+ const errorMessage = `Failed to update comment: ${error instanceof Error ? error.message : "Unknown error"}`;
2326
+ await callback?.({ text: errorMessage, source: message.content.source });
2327
+ return { text: errorMessage, success: false };
2328
+ }
2329
+ }
2330
+
2331
+ // src/actions/updateIssue.ts
2332
+ import {
2333
+ logger as logger11,
2334
+ ModelType as ModelType7
2335
+ } from "@elizaos/core";
2336
+ var LINEAR_MODEL_TIMEOUT_MS2 = 15000;
2337
+ var LINEAR_LOOKUP_LIMIT = 100;
2338
+ async function handleUpdateIssue(runtime, message, _state, _options, callback) {
2339
+ try {
2340
+ const linearService = runtime.getService("linear");
2341
+ if (!linearService) {
2342
+ throw new Error("Linear service not available");
2335
2343
  }
2336
- },
2337
- async handler(runtime, message, _state, _options, callback) {
2338
- try {
2339
- const linearService = runtime.getService("linear");
2340
- if (!linearService) {
2341
- throw new Error("Linear service not available");
2342
- }
2343
- const content = message.content.text;
2344
- if (!content) {
2345
- const errorMessage = "Please provide update instructions for the issue.";
2346
- await callback?.({
2347
- text: errorMessage,
2348
- source: message.content.source
2349
- });
2350
- return {
2351
- text: errorMessage,
2352
- success: false
2353
- };
2354
- }
2355
- const prompt = updateIssueTemplate.replace("{{userMessage}}", content);
2356
- const response = await runtime.useModel(ModelType9.TEXT_LARGE, {
2357
- prompt
2344
+ const accountId = getLinearAccountId(runtime, _options);
2345
+ const content = message.content.text;
2346
+ if (!content) {
2347
+ const errorMessage = "Please provide update instructions for the issue.";
2348
+ await callback?.({
2349
+ text: errorMessage,
2350
+ source: message.content.source
2358
2351
  });
2359
- if (!response) {
2360
- throw new Error("Failed to extract update information");
2361
- }
2362
- let issueId;
2363
- const updates = {};
2364
- try {
2365
- const cleanedResponse = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
2366
- const parsed = JSON.parse(cleanedResponse);
2367
- issueId = parsed.issueId;
2368
- if (!issueId) {
2369
- throw new Error("Issue ID not found in parsed response");
2370
- }
2371
- if (parsed.updates?.title) {
2372
- updates.title = parsed.updates.title;
2373
- }
2374
- if (parsed.updates?.description) {
2375
- updates.description = parsed.updates.description;
2376
- }
2377
- if (parsed.updates?.priority) {
2378
- updates.priority = Number(parsed.updates.priority);
2379
- }
2380
- if (parsed.updates?.teamKey) {
2381
- const teams = await linearService.getTeams();
2382
- const team = teams.find((t) => t.key.toLowerCase() === parsed.updates.teamKey.toLowerCase());
2383
- if (team) {
2384
- updates.teamId = team.id;
2385
- logger10.info(`Moving issue to team: ${team.name} (${team.key})`);
2386
- } else {
2387
- logger10.warn(`Team with key ${parsed.updates.teamKey} not found`);
2388
- }
2352
+ return {
2353
+ text: errorMessage,
2354
+ success: false
2355
+ };
2356
+ }
2357
+ const prompt = updateIssueTemplate.replace("{{userMessage}}", content);
2358
+ const response = await Promise.race([
2359
+ runtime.useModel(ModelType7.TEXT_LARGE, {
2360
+ prompt
2361
+ }),
2362
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Linear update extraction timeout")), LINEAR_MODEL_TIMEOUT_MS2))
2363
+ ]);
2364
+ if (!response) {
2365
+ throw new Error("Failed to extract update information");
2366
+ }
2367
+ let issueId;
2368
+ const updates = {};
2369
+ try {
2370
+ const parsed = parseLinearPromptResponse(response);
2371
+ if (Object.keys(parsed).length === 0) {
2372
+ throw new Error("No fields found in model response");
2373
+ }
2374
+ issueId = getStringValue(parsed.issueId) ?? "";
2375
+ if (!issueId) {
2376
+ throw new Error("Issue ID not found in parsed response");
2377
+ }
2378
+ const parsedUpdates = getRecordValue(parsed.updates) ?? {};
2379
+ const title = getStringValue(parsedUpdates.title);
2380
+ if (title) {
2381
+ updates.title = title;
2382
+ }
2383
+ const description = getStringValue(parsedUpdates.description);
2384
+ if (description) {
2385
+ updates.description = description;
2386
+ }
2387
+ const priority = getPriorityNumberValue(parsedUpdates.priority);
2388
+ if (priority) {
2389
+ updates.priority = priority;
2390
+ }
2391
+ const teamKey = getStringValue(parsedUpdates.teamKey);
2392
+ if (teamKey) {
2393
+ const teams = await linearService.getTeams(accountId);
2394
+ const team = teams.slice(0, LINEAR_LOOKUP_LIMIT).find((t) => t.key.toLowerCase() === teamKey.toLowerCase());
2395
+ if (team) {
2396
+ updates.teamId = team.id;
2397
+ logger11.info(`Moving issue to team: ${team.name} (${team.key})`);
2398
+ } else {
2399
+ logger11.warn(`Team with key ${teamKey} not found`);
2389
2400
  }
2390
- if (parsed.updates?.assignee) {
2391
- const cleanAssignee = parsed.updates.assignee.replace(/^@/, "");
2392
- const users = await linearService.getUsers();
2393
- const user = users.find((u) => u.email === cleanAssignee || u.name.toLowerCase().includes(cleanAssignee.toLowerCase()));
2394
- if (user) {
2395
- updates.assigneeId = user.id;
2396
- } else {
2397
- logger10.warn(`User ${cleanAssignee} not found`);
2398
- }
2401
+ }
2402
+ const assignee = getStringValue(parsedUpdates.assignee);
2403
+ if (assignee) {
2404
+ const cleanAssignee = assignee.replace(/^@/, "");
2405
+ const users = await linearService.getUsers(accountId);
2406
+ const user = users.slice(0, LINEAR_LOOKUP_LIMIT).find((u) => u.email === cleanAssignee || u.name.toLowerCase().includes(cleanAssignee.toLowerCase()));
2407
+ if (user) {
2408
+ updates.assigneeId = user.id;
2409
+ } else {
2410
+ logger11.warn(`User ${cleanAssignee} not found`);
2399
2411
  }
2400
- if (parsed.updates?.status) {
2401
- const issue = await linearService.getIssue(issueId);
2402
- const issueTeam = await issue.team;
2403
- const teamId = updates.teamId || issueTeam?.id;
2404
- if (!teamId) {
2405
- logger10.warn("Could not determine team for status update");
2412
+ }
2413
+ const status = getStringValue(parsedUpdates.status);
2414
+ if (status) {
2415
+ const issue = await linearService.getIssue(issueId, accountId);
2416
+ const issueTeam = await issue.team;
2417
+ const teamId = updates.teamId || issueTeam?.id;
2418
+ if (!teamId) {
2419
+ logger11.warn("Could not determine team for status update");
2420
+ } else {
2421
+ const states = await linearService.getWorkflowStates(teamId, accountId);
2422
+ const state = states.slice(0, LINEAR_LOOKUP_LIMIT).find((s) => s.name.toLowerCase() === status.toLowerCase() || s.type.toLowerCase() === status.toLowerCase());
2423
+ if (state) {
2424
+ updates.stateId = state.id;
2425
+ logger11.info(`Changing status to: ${state.name}`);
2406
2426
  } else {
2407
- const states = await linearService.getWorkflowStates(teamId);
2408
- const state = states.find((s) => s.name.toLowerCase() === parsed.updates.status.toLowerCase() || s.type.toLowerCase() === parsed.updates.status.toLowerCase());
2409
- if (state) {
2410
- updates.stateId = state.id;
2411
- logger10.info(`Changing status to: ${state.name}`);
2412
- } else {
2413
- logger10.warn(`Status ${parsed.updates.status} not found for team`);
2414
- }
2427
+ logger11.warn(`Status ${status} not found for team`);
2415
2428
  }
2416
2429
  }
2417
- if (parsed.updates?.labels && Array.isArray(parsed.updates.labels)) {
2418
- const teamId = updates.teamId;
2419
- const labels = await linearService.getLabels(teamId);
2420
- const labelIds = [];
2421
- for (const labelName of parsed.updates.labels) {
2422
- if (labelName) {
2423
- const label = labels.find((l) => l.name.toLowerCase() === labelName.toLowerCase());
2424
- if (label) {
2425
- labelIds.push(label.id);
2426
- }
2427
- }
2428
- }
2429
- updates.labelIds = labelIds;
2430
- }
2431
- } catch (parseError) {
2432
- logger10.warn("Failed to parse LLM response, falling back to regex parsing:", parseError);
2433
- const issueMatch = content.match(/(\w+-\d+)/);
2434
- if (!issueMatch) {
2435
- const errorMessage = "Please specify an issue ID (e.g., ENG-123) to update.";
2436
- await callback?.({
2437
- text: errorMessage,
2438
- source: message.content.source
2439
- });
2440
- return {
2441
- text: errorMessage,
2442
- success: false
2443
- };
2444
- }
2445
- issueId = issueMatch[1];
2446
- const titleMatch = content.match(/title to ["'](.+?)["']/i);
2447
- if (titleMatch) {
2448
- updates.title = titleMatch[1];
2449
- }
2450
- const priorityMatch = content.match(/priority (?:to |as )?(\w+)/i);
2451
- if (priorityMatch) {
2452
- const priorityMap = {
2453
- urgent: 1,
2454
- high: 2,
2455
- normal: 3,
2456
- medium: 3,
2457
- low: 4
2458
- };
2459
- const priority = priorityMap[priorityMatch[1].toLowerCase()];
2460
- if (priority) {
2461
- updates.priority = priority;
2430
+ }
2431
+ const parsedLabels = getStringArrayValue(parsedUpdates.labels);
2432
+ if (parsedLabels !== undefined) {
2433
+ const teamId = updates.teamId;
2434
+ const labels = await linearService.getLabels(teamId, accountId);
2435
+ const labelIds = [];
2436
+ for (const labelName of parsedLabels.slice(0, LINEAR_LOOKUP_LIMIT)) {
2437
+ const label = labels.slice(0, LINEAR_LOOKUP_LIMIT).find((l) => l.name.toLowerCase() === labelName.toLowerCase());
2438
+ if (label) {
2439
+ labelIds.push(label.id);
2462
2440
  }
2463
2441
  }
2442
+ updates.labelIds = labelIds;
2464
2443
  }
2465
- if (Object.keys(updates).length === 0) {
2466
- const errorMessage = `No valid updates found. Please specify what to update (e.g., "Update issue ENG-123 title to 'New Title'")`;
2444
+ } catch (parseError) {
2445
+ logger11.warn("Failed to parse LLM response, falling back to regex parsing:", parseError);
2446
+ const issueMatch = content.match(/(\w+-\d+)/);
2447
+ if (!issueMatch) {
2448
+ const errorMessage = "Please specify an issue ID (e.g., ENG-123) to update.";
2467
2449
  await callback?.({
2468
2450
  text: errorMessage,
2469
2451
  source: message.content.source
@@ -2473,43 +2455,28 @@ var updateIssueAction = {
2473
2455
  success: false
2474
2456
  };
2475
2457
  }
2476
- const updatedIssue = await linearService.updateIssue(issueId, updates);
2477
- const updateSummary = [];
2478
- if (updates.title)
2479
- updateSummary.push(`title: "${updates.title}"`);
2480
- if (updates.priority)
2481
- updateSummary.push(`priority: ${["", "urgent", "high", "normal", "low"][updates.priority]}`);
2482
- if (updates.teamId)
2483
- updateSummary.push(`moved to team`);
2484
- if (updates.assigneeId)
2485
- updateSummary.push(`assigned to user`);
2486
- if (updates.stateId)
2487
- updateSummary.push(`status changed`);
2488
- if (updates.labelIds)
2489
- updateSummary.push(`labels updated`);
2490
- const successMessage = `✅ Updated issue ${updatedIssue.identifier}: ${updateSummary.join(", ")}
2491
-
2492
- View it at: ${updatedIssue.url}`;
2493
- await callback?.({
2494
- text: successMessage,
2495
- source: message.content.source
2496
- });
2497
- return {
2498
- text: `Updated issue ${updatedIssue.identifier}: ${updateSummary.join(", ")}`,
2499
- success: true,
2500
- data: {
2501
- issueId: updatedIssue.id,
2502
- identifier: updatedIssue.identifier,
2503
- updates: updates ? Object.fromEntries(Object.entries(updates).map(([key, value]) => [
2504
- key,
2505
- value instanceof Date ? value.toISOString() : value
2506
- ])) : undefined,
2507
- url: updatedIssue.url
2458
+ issueId = issueMatch[1];
2459
+ const titleMatch = content.match(/title to ["'](.+?)["']/i);
2460
+ if (titleMatch) {
2461
+ updates.title = titleMatch[1];
2462
+ }
2463
+ const priorityMatch = content.match(/priority (?:to |as )?(\w+)/i);
2464
+ if (priorityMatch) {
2465
+ const priorityMap = {
2466
+ urgent: 1,
2467
+ high: 2,
2468
+ normal: 3,
2469
+ medium: 3,
2470
+ low: 4
2471
+ };
2472
+ const priority = priorityMap[priorityMatch[1].toLowerCase()];
2473
+ if (priority) {
2474
+ updates.priority = priority;
2508
2475
  }
2509
- };
2510
- } catch (error) {
2511
- logger10.error("Failed to update issue:", error);
2512
- const errorMessage = `❌ Failed to update issue: ${error instanceof Error ? error.message : "Unknown error"}`;
2476
+ }
2477
+ }
2478
+ if (Object.keys(updates).length === 0) {
2479
+ const errorMessage = `No valid updates found. Please specify what to update (e.g., "Update issue ENG-123 title to 'New Title'")`;
2513
2480
  await callback?.({
2514
2481
  text: errorMessage,
2515
2482
  source: message.content.source
@@ -2519,14 +2486,484 @@ View it at: ${updatedIssue.url}`;
2519
2486
  success: false
2520
2487
  };
2521
2488
  }
2489
+ const updatedIssue = await linearService.updateIssue(issueId, updates, accountId);
2490
+ const updateSummary = [];
2491
+ if (updates.title)
2492
+ updateSummary.push(`title: "${updates.title}"`);
2493
+ if (updates.priority)
2494
+ updateSummary.push(`priority: ${["", "urgent", "high", "normal", "low"][updates.priority]}`);
2495
+ if (updates.teamId)
2496
+ updateSummary.push(`moved to team`);
2497
+ if (updates.assigneeId)
2498
+ updateSummary.push(`assigned to user`);
2499
+ if (updates.stateId)
2500
+ updateSummary.push(`status changed`);
2501
+ if (updates.labelIds)
2502
+ updateSummary.push(`labels updated`);
2503
+ const successMessage = `✅ Updated issue ${updatedIssue.identifier}: ${updateSummary.join(", ")}
2504
+
2505
+ View it at: ${updatedIssue.url}`;
2506
+ await callback?.({
2507
+ text: successMessage,
2508
+ source: message.content.source
2509
+ });
2510
+ return {
2511
+ text: `Updated issue ${updatedIssue.identifier}: ${updateSummary.join(", ")}`,
2512
+ success: true,
2513
+ data: {
2514
+ issueId: updatedIssue.id,
2515
+ identifier: updatedIssue.identifier,
2516
+ updates: updates ? Object.fromEntries(Object.entries(updates).map(([key, value]) => [
2517
+ key,
2518
+ value instanceof Date ? value.toISOString() : value
2519
+ ])) : undefined,
2520
+ url: updatedIssue.url,
2521
+ accountId
2522
+ }
2523
+ };
2524
+ } catch (error) {
2525
+ logger11.error("Failed to update issue:", error);
2526
+ const errorMessage = `❌ Failed to update issue: ${error instanceof Error ? error.message : "Unknown error"}`;
2527
+ await callback?.({
2528
+ text: errorMessage,
2529
+ source: message.content.source
2530
+ });
2531
+ return {
2532
+ text: errorMessage,
2533
+ success: false
2534
+ };
2535
+ }
2536
+ }
2537
+
2538
+ // src/actions/linear.ts
2539
+ var LINEAR_CONTEXT = "linear";
2540
+ var ALL_OPS = [
2541
+ "create_issue",
2542
+ "get_issue",
2543
+ "update_issue",
2544
+ "delete_issue",
2545
+ "create_comment",
2546
+ "update_comment",
2547
+ "delete_comment",
2548
+ "list_comments",
2549
+ "get_activity",
2550
+ "clear_activity",
2551
+ "search_issues"
2552
+ ];
2553
+ var ROUTES = [
2554
+ {
2555
+ op: "delete_issue",
2556
+ action: deleteIssueAction,
2557
+ match: /\b(delete|archive|remove|close)\b.*\b(issue|bug|task|ticket|[a-z]+-\d+)\b/i
2558
+ },
2559
+ {
2560
+ op: "update_issue",
2561
+ run: handleUpdateIssue,
2562
+ match: /\b(update|edit|modify|move|change|assign|reassign|priority|status|label)\b.*\b(issue|bug|task|ticket|[a-z]+-\d+)\b/i
2563
+ },
2564
+ {
2565
+ op: "create_issue",
2566
+ action: createIssueAction,
2567
+ match: /\b(create|new|add|file|open)\b.*\b(issue|bug|task|ticket|linear)\b|\b(issue|bug|task|ticket)\b.*\b(create|new|add|file|open)\b/i
2568
+ },
2569
+ {
2570
+ op: "create_comment",
2571
+ action: createCommentAction,
2572
+ match: /\b(comment|reply|note|tell)\b.*\b(issue|bug|task|ticket|[a-z]+-\d+)\b/i
2573
+ },
2574
+ {
2575
+ op: "update_comment",
2576
+ run: handleUpdateComment,
2577
+ match: /\b(update|edit|modify|change)\b.*\bcomment\b/i
2578
+ },
2579
+ {
2580
+ op: "delete_comment",
2581
+ action: deleteCommentAction,
2582
+ match: /\b(delete|remove|erase)\b.*\bcomment\b/i
2583
+ },
2584
+ {
2585
+ op: "list_comments",
2586
+ action: listCommentsAction,
2587
+ match: /\b(list|show|get|fetch|view)\b.*\bcomments?\b|\bcomments?\b.*\b(list|show|get|fetch)\b/i
2588
+ },
2589
+ {
2590
+ op: "clear_activity",
2591
+ action: clearActivityAction,
2592
+ match: /\b(clear|reset|delete)\b.*\b(activity|activity log)\b/i
2593
+ },
2594
+ {
2595
+ op: "get_activity",
2596
+ action: getActivityAction,
2597
+ match: /\b(activity|activity log|what happened|recent changes|audit)\b/i
2598
+ },
2599
+ {
2600
+ op: "search_issues",
2601
+ action: searchIssuesAction,
2602
+ match: /\b(search|find|query|list|show)\b.*\b(issues?|bugs?|tasks?|tickets?)\b|\b(open|closed|unassigned|assigned|high priority|blockers?)\b.*\b(issues?|bugs?|tasks?|tickets?)\b/i
2603
+ },
2604
+ {
2605
+ op: "get_issue",
2606
+ action: getIssueAction,
2607
+ match: /\b(show|get|view|check|details?|status|what'?s|find)\b.*\b(issue|bug|task|ticket|[a-z]+-\d+)\b|[a-z]+-\d+/i
2522
2608
  }
2609
+ ];
2610
+ function textOf(message) {
2611
+ return typeof message.content?.text === "string" ? message.content.text : "";
2612
+ }
2613
+ function readOptions(options) {
2614
+ const direct = options ?? {};
2615
+ const parameters = direct.parameters && typeof direct.parameters === "object" ? direct.parameters : {};
2616
+ return { ...direct, ...parameters };
2617
+ }
2618
+ function normalizeOp(value) {
2619
+ if (typeof value !== "string")
2620
+ return null;
2621
+ const trimmed = value.trim().toLowerCase().replace(/[\s-]+/g, "_");
2622
+ return ALL_OPS.includes(trimmed) ? trimmed : null;
2623
+ }
2624
+ function selectRoute(message, options) {
2625
+ const opts = readOptions(options);
2626
+ const requested = normalizeOp(opts.action ?? opts.subaction ?? opts.op);
2627
+ if (requested) {
2628
+ const route = ROUTES.find((candidate) => candidate.op === requested);
2629
+ if (route)
2630
+ return route;
2631
+ }
2632
+ const text = textOf(message);
2633
+ return ROUTES.find((route) => route.match.test(text)) ?? null;
2634
+ }
2635
+ function hasLinearAccess(runtime) {
2636
+ return hasLinearAccountConfig(runtime);
2637
+ }
2638
+ var linearAction = {
2639
+ name: "LINEAR",
2640
+ description: "Manage Linear issues, comments, and activity. Operations: create_issue, get_issue, update_issue, delete_issue, create_comment, update_comment, delete_comment, list_comments, get_activity, clear_activity, search_issues. The op is inferred from the message text when not explicitly provided.",
2641
+ descriptionCompressed: "Linear: create/get/update/delete issue, create/update/delete/list comment, search issues, get/clear activity.",
2642
+ similes: [
2643
+ "LINEAR_ISSUE",
2644
+ "LINEAR_ISSUES",
2645
+ "LINEAR_COMMENT",
2646
+ "LINEAR_COMMENTS",
2647
+ "LINEAR_WORKFLOW",
2648
+ "LINEAR_ACTIVITY",
2649
+ "LINEAR_SEARCH",
2650
+ "CREATE_LINEAR_ISSUE",
2651
+ "GET_LINEAR_ISSUE",
2652
+ "UPDATE_LINEAR_ISSUE",
2653
+ "DELETE_LINEAR_ISSUE",
2654
+ "MANAGE_LINEAR_ISSUE",
2655
+ "MANAGE_LINEAR_ISSUES",
2656
+ "CREATE_LINEAR_COMMENT",
2657
+ "COMMENT_LINEAR_ISSUE",
2658
+ "UPDATE_LINEAR_COMMENT",
2659
+ "DELETE_LINEAR_COMMENT",
2660
+ "LIST_LINEAR_COMMENTS",
2661
+ "GET_LINEAR_ACTIVITY",
2662
+ "CLEAR_LINEAR_ACTIVITY",
2663
+ "SEARCH_LINEAR_ISSUES",
2664
+ "LINEAR_WORKFLOW_SEARCH"
2665
+ ],
2666
+ contexts: ["general", "automation", "knowledge", LINEAR_CONTEXT],
2667
+ contextGate: { anyOf: ["general", "automation", "knowledge", LINEAR_CONTEXT] },
2668
+ roleGate: { minRole: "USER" },
2669
+ parameters: [
2670
+ {
2671
+ name: "action",
2672
+ description: "Operation to perform. One of: create_issue, get_issue, update_issue, delete_issue, create_comment, update_comment, delete_comment, list_comments, get_activity, clear_activity, search_issues. Inferred from message text when omitted.",
2673
+ required: false,
2674
+ schema: { type: "string", enum: [...ALL_OPS] }
2675
+ },
2676
+ linearAccountIdParameter
2677
+ ],
2678
+ validate: async (runtime) => {
2679
+ if (!hasLinearAccess(runtime))
2680
+ return false;
2681
+ return true;
2682
+ },
2683
+ handler: async (runtime, message, state, options, callback) => {
2684
+ const route = selectRoute(message, options);
2685
+ if (!route) {
2686
+ const ops = ALL_OPS.join(", ");
2687
+ const text = `LINEAR could not determine the operation. Specify one of: ${ops}.`;
2688
+ await callback?.({ text, source: message.content?.source });
2689
+ return {
2690
+ success: false,
2691
+ text,
2692
+ values: { error: "MISSING" },
2693
+ data: { actionName: "LINEAR", availableOps: ops }
2694
+ };
2695
+ }
2696
+ const dispatch = route.run ?? route.action?.handler?.bind(route.action);
2697
+ const result = dispatch ? await dispatch(runtime, message, state, options, callback) ?? { success: true } : { success: true };
2698
+ return {
2699
+ ...result,
2700
+ data: {
2701
+ ...typeof result.data === "object" && result.data ? result.data : {},
2702
+ actionName: "LINEAR",
2703
+ routedActionName: route.action?.name ?? route.op,
2704
+ op: route.op
2705
+ }
2706
+ };
2707
+ },
2708
+ examples: [
2709
+ [
2710
+ { name: "{{user1}}", content: { text: "Create a Linear issue for the mobile login bug" } },
2711
+ {
2712
+ name: "{{agentName}}",
2713
+ content: {
2714
+ text: "I'll create that Linear issue.",
2715
+ actions: ["LINEAR"]
2716
+ }
2717
+ }
2718
+ ],
2719
+ [
2720
+ { name: "{{user1}}", content: { text: "Comment on ENG-123 that QA can retest it" } },
2721
+ {
2722
+ name: "{{agentName}}",
2723
+ content: { text: "I'll add that comment to ENG-123.", actions: ["LINEAR"] }
2724
+ }
2725
+ ],
2726
+ [
2727
+ { name: "{{user1}}", content: { text: "Search open Linear bugs for the backend team" } },
2728
+ {
2729
+ name: "{{agentName}}",
2730
+ content: { text: "I'll search Linear issues.", actions: ["LINEAR"] }
2731
+ }
2732
+ ],
2733
+ [
2734
+ { name: "{{user1}}", content: { text: "What's the status of ENG-456?" } },
2735
+ {
2736
+ name: "{{agentName}}",
2737
+ content: { text: "Looking up ENG-456.", actions: ["LINEAR"] }
2738
+ }
2739
+ ]
2740
+ ]
2523
2741
  };
2524
2742
 
2743
+ // src/connector-account-provider.ts
2744
+ import {
2745
+ logger as logger12
2746
+ } from "@elizaos/core";
2747
+ var LINEAR_PROVIDER_NAME = "linear";
2748
+ var LINEAR_AUTHORIZATION_ENDPOINT = "https://linear.app/oauth/authorize";
2749
+ var LINEAR_TOKEN_ENDPOINT = "https://api.linear.app/oauth/token";
2750
+ var LINEAR_GRAPHQL_ENDPOINT = "https://api.linear.app/graphql";
2751
+ var DEFAULT_PURPOSES = ["admin"];
2752
+ function nonEmptyString2(value) {
2753
+ if (typeof value !== "string")
2754
+ return;
2755
+ const trimmed = value.trim();
2756
+ return trimmed.length > 0 ? trimmed : undefined;
2757
+ }
2758
+ function readSetting2(runtime, key) {
2759
+ return nonEmptyString2(runtime.getSetting?.(key));
2760
+ }
2761
+ function readClientConfig(runtime) {
2762
+ const clientId = readSetting2(runtime, "LINEAR_OAUTH_CLIENT_ID");
2763
+ const clientSecret = readSetting2(runtime, "LINEAR_OAUTH_CLIENT_SECRET");
2764
+ const redirectUri = readSetting2(runtime, "LINEAR_OAUTH_REDIRECT_URI");
2765
+ if (!clientId || !clientSecret || !redirectUri) {
2766
+ throw new Error("Linear OAuth requires LINEAR_OAUTH_CLIENT_ID, LINEAR_OAUTH_CLIENT_SECRET, and LINEAR_OAUTH_REDIRECT_URI to be configured.");
2767
+ }
2768
+ return { clientId, clientSecret, redirectUri };
2769
+ }
2770
+ function parseScopes(value) {
2771
+ if (!value)
2772
+ return [];
2773
+ return value.split(/[,\s]+/).map((scope) => scope.trim()).filter(Boolean);
2774
+ }
2775
+ async function exchangeCodeForToken(args) {
2776
+ const response = await fetch(LINEAR_TOKEN_ENDPOINT, {
2777
+ method: "POST",
2778
+ headers: {
2779
+ "Content-Type": "application/x-www-form-urlencoded",
2780
+ Accept: "application/json"
2781
+ },
2782
+ body: new URLSearchParams({
2783
+ client_id: args.clientId,
2784
+ client_secret: args.clientSecret,
2785
+ code: args.code,
2786
+ redirect_uri: args.redirectUri,
2787
+ grant_type: "authorization_code"
2788
+ }).toString()
2789
+ });
2790
+ if (!response.ok) {
2791
+ const body = await response.text();
2792
+ throw new Error(`Linear token exchange failed with ${response.status}: ${body}`);
2793
+ }
2794
+ const parsed = await response.json();
2795
+ if (parsed.error) {
2796
+ throw new Error(`Linear token exchange returned error ${parsed.error}: ${parsed.error_description ?? "no description"}`);
2797
+ }
2798
+ if (!parsed.access_token) {
2799
+ throw new Error("Linear token exchange returned no access_token.");
2800
+ }
2801
+ return parsed;
2802
+ }
2803
+ async function fetchLinearViewer(accessToken) {
2804
+ const response = await fetch(LINEAR_GRAPHQL_ENDPOINT, {
2805
+ method: "POST",
2806
+ headers: {
2807
+ Authorization: `Bearer ${accessToken}`,
2808
+ "Content-Type": "application/json"
2809
+ },
2810
+ body: JSON.stringify({
2811
+ query: "{ viewer { id name email organization { id name urlKey } } }"
2812
+ })
2813
+ });
2814
+ if (!response.ok) {
2815
+ throw new Error(`Linear viewer query failed with ${response.status}`);
2816
+ }
2817
+ return await response.json();
2818
+ }
2819
+ function synthesizeEnvAccounts(runtime) {
2820
+ const now = Date.now();
2821
+ return readLinearAccounts(runtime).map((account) => ({
2822
+ id: account.accountId,
2823
+ provider: LINEAR_PROVIDER_NAME,
2824
+ label: account.label ?? `Linear (${account.accountId})`,
2825
+ role: "OWNER",
2826
+ purpose: DEFAULT_PURPOSES,
2827
+ accessGate: "open",
2828
+ status: "connected",
2829
+ externalId: account.workspaceId,
2830
+ displayHandle: account.workspaceId ?? account.accountId,
2831
+ createdAt: now,
2832
+ updatedAt: now,
2833
+ metadata: {
2834
+ authMethod: "api_key",
2835
+ source: "env",
2836
+ defaultTeamKey: account.defaultTeamKey ?? null
2837
+ }
2838
+ }));
2839
+ }
2840
+ function createLinearConnectorAccountProvider(runtime) {
2841
+ return {
2842
+ provider: LINEAR_PROVIDER_NAME,
2843
+ label: "Linear",
2844
+ listAccounts: async (manager) => {
2845
+ const stored = await manager.getStorage().listAccounts(LINEAR_PROVIDER_NAME);
2846
+ if (stored.length > 0)
2847
+ return stored;
2848
+ return synthesizeEnvAccounts(runtime);
2849
+ },
2850
+ createAccount: async (input, _manager) => {
2851
+ return {
2852
+ ...input,
2853
+ provider: LINEAR_PROVIDER_NAME,
2854
+ role: input.role ?? "OWNER",
2855
+ purpose: input.purpose ?? DEFAULT_PURPOSES,
2856
+ accessGate: input.accessGate ?? "open",
2857
+ status: input.status ?? "pending"
2858
+ };
2859
+ },
2860
+ patchAccount: async (_accountId, patch, _manager) => {
2861
+ return { ...patch, provider: LINEAR_PROVIDER_NAME };
2862
+ },
2863
+ deleteAccount: async (_accountId, _manager) => {},
2864
+ startOAuth: async (request, _manager) => {
2865
+ const config = readClientConfig(runtime);
2866
+ const redirectUri = request.redirectUri ?? config.redirectUri;
2867
+ const scopes = request.scopes && request.scopes.length > 0 ? request.scopes : ["read", "write", "issues:create", "comments:create"];
2868
+ const params = new URLSearchParams({
2869
+ client_id: config.clientId,
2870
+ redirect_uri: redirectUri,
2871
+ response_type: "code",
2872
+ scope: scopes.join(","),
2873
+ state: request.flow.state,
2874
+ prompt: "consent"
2875
+ });
2876
+ return {
2877
+ authUrl: `${LINEAR_AUTHORIZATION_ENDPOINT}?${params.toString()}`,
2878
+ metadata: {
2879
+ ...request.metadata,
2880
+ requestedScopes: scopes,
2881
+ redirectUri
2882
+ }
2883
+ };
2884
+ },
2885
+ completeOAuth: async (request, _manager) => {
2886
+ const code = nonEmptyString2(request.code);
2887
+ if (!code) {
2888
+ throw new Error("Linear OAuth callback is missing an authorization code.");
2889
+ }
2890
+ const config = readClientConfig(runtime);
2891
+ const redirectUri = nonEmptyString2(request.flow.redirectUri) ?? config.redirectUri;
2892
+ const tokens = await exchangeCodeForToken({
2893
+ clientId: config.clientId,
2894
+ clientSecret: config.clientSecret,
2895
+ redirectUri,
2896
+ code
2897
+ });
2898
+ if (!tokens.access_token) {
2899
+ throw new Error("Linear token exchange returned no access_token.");
2900
+ }
2901
+ const viewerPayload = await fetchLinearViewer(tokens.access_token);
2902
+ const viewer = viewerPayload.data?.viewer;
2903
+ const organization = viewer?.organization;
2904
+ const workspaceId = nonEmptyString2(organization?.id);
2905
+ const workspaceHandle = nonEmptyString2(organization?.urlKey);
2906
+ const externalId = workspaceId ?? workspaceHandle;
2907
+ if (!externalId) {
2908
+ throw new Error("Linear viewer payload did not include an organization id or urlKey.");
2909
+ }
2910
+ const accountPatch = {
2911
+ provider: LINEAR_PROVIDER_NAME,
2912
+ role: "OWNER",
2913
+ purpose: DEFAULT_PURPOSES,
2914
+ accessGate: "open",
2915
+ status: "connected",
2916
+ externalId,
2917
+ displayHandle: workspaceHandle ?? externalId,
2918
+ label: nonEmptyString2(organization?.name) ?? nonEmptyString2(workspaceHandle) ?? "Linear",
2919
+ metadata: {
2920
+ authMethod: "oauth",
2921
+ workspaceId: workspaceId ?? null,
2922
+ workspaceHandle: workspaceHandle ?? null,
2923
+ workspaceName: nonEmptyString2(organization?.name) ?? null,
2924
+ viewerId: nonEmptyString2(viewer?.id) ?? null,
2925
+ viewerEmail: nonEmptyString2(viewer?.email) ?? null,
2926
+ viewerName: nonEmptyString2(viewer?.name) ?? null,
2927
+ tokenType: nonEmptyString2(tokens.token_type) ?? "bearer",
2928
+ grantedScopes: parseScopes(tokens.scope),
2929
+ hasRefreshToken: Boolean(tokens.refresh_token)
2930
+ }
2931
+ };
2932
+ logger12.info({
2933
+ src: "plugin:linear:connector",
2934
+ workspaceId: workspaceId ?? null,
2935
+ workspaceHandle: workspaceHandle ?? null
2936
+ }, "Linear OAuth completed");
2937
+ return {
2938
+ account: accountPatch,
2939
+ flow: { status: "completed" }
2940
+ };
2941
+ }
2942
+ };
2943
+ }
2944
+
2525
2945
  // src/providers/activity.ts
2946
+ function formatDetails(details) {
2947
+ if (details === null || details === undefined) {
2948
+ return "none";
2949
+ }
2950
+ if (Array.isArray(details)) {
2951
+ return details.map(formatDetails).join(", ");
2952
+ }
2953
+ if (typeof details !== "object") {
2954
+ return String(details);
2955
+ }
2956
+ return Object.entries(details).map(([key, value]) => `${key}: ${formatDetails(value)}`).join("; ");
2957
+ }
2526
2958
  var linearActivityProvider = {
2527
2959
  name: "LINEAR_ACTIVITY",
2528
2960
  description: "Provides context about recent Linear activity",
2961
+ descriptionCompressed: "provide context recent Linear activity",
2529
2962
  dynamic: true,
2963
+ contexts: ["automation", "connectors"],
2964
+ contextGate: { anyOf: ["automation", "connectors"] },
2965
+ cacheScope: "turn",
2966
+ roleGate: { minRole: "ADMIN" },
2530
2967
  get: async (runtime, _message, _state) => {
2531
2968
  try {
2532
2969
  const linearService = runtime.getService("linear");
@@ -2559,7 +2996,7 @@ ${activityList.join(`
2559
2996
  resource_id: item.resource_id,
2560
2997
  success: item.success,
2561
2998
  error: item.error,
2562
- details: JSON.stringify(item.details),
2999
+ details: formatDetails(item.details),
2563
3000
  timestamp: typeof item.timestamp === "string" ? item.timestamp : new Date(item.timestamp).toISOString()
2564
3001
  }))
2565
3002
  }
@@ -2576,7 +3013,12 @@ ${activityList.join(`
2576
3013
  var linearIssuesProvider = {
2577
3014
  name: "LINEAR_ISSUES",
2578
3015
  description: "Provides context about recent Linear issues",
3016
+ descriptionCompressed: "provide context recent Linear issue",
2579
3017
  dynamic: true,
3018
+ contexts: ["automation", "connectors"],
3019
+ contextGate: { anyOf: ["automation", "connectors"] },
3020
+ cacheScope: "turn",
3021
+ roleGate: { minRole: "ADMIN" },
2580
3022
  get: async (runtime, _message, _state) => {
2581
3023
  try {
2582
3024
  const linearService = runtime.getService("linear");
@@ -2620,7 +3062,12 @@ ${issuesList.join(`
2620
3062
  var linearProjectsProvider = {
2621
3063
  name: "LINEAR_PROJECTS",
2622
3064
  description: "Provides context about active Linear projects",
3065
+ descriptionCompressed: "provide context active Linear project",
2623
3066
  dynamic: true,
3067
+ contexts: ["automation", "connectors"],
3068
+ contextGate: { anyOf: ["automation", "connectors"] },
3069
+ cacheScope: "agent",
3070
+ roleGate: { minRole: "ADMIN" },
2624
3071
  get: async (runtime, _message, _state) => {
2625
3072
  try {
2626
3073
  const linearService = runtime.getService("linear");
@@ -2659,10 +3106,17 @@ ${projectsList.join(`
2659
3106
  };
2660
3107
 
2661
3108
  // src/providers/teams.ts
3109
+ var MAX_LINEAR_TEAMS = 20;
3110
+ var MAX_DESCRIPTION_CHARS = 180;
2662
3111
  var linearTeamsProvider = {
2663
3112
  name: "LINEAR_TEAMS",
2664
3113
  description: "Provides context about Linear teams",
3114
+ descriptionCompressed: "provide context Linear team",
2665
3115
  dynamic: true,
3116
+ contexts: ["automation", "connectors"],
3117
+ contextGate: { anyOf: ["automation", "connectors"] },
3118
+ cacheScope: "agent",
3119
+ roleGate: { minRole: "ADMIN" },
2666
3120
  get: async (runtime, _message, _state) => {
2667
3121
  try {
2668
3122
  const linearService = runtime.getService("linear");
@@ -2672,23 +3126,25 @@ var linearTeamsProvider = {
2672
3126
  };
2673
3127
  }
2674
3128
  const teams = await linearService.getTeams();
3129
+ const listedTeams = teams.slice(0, MAX_LINEAR_TEAMS);
2675
3130
  if (teams.length === 0) {
2676
3131
  return {
2677
3132
  text: "No Linear teams found"
2678
3133
  };
2679
3134
  }
2680
- const teamsList = teams.map((team) => `- ${team.name} (${team.key}): ${team.description || "No description"}`);
3135
+ const teamsList = listedTeams.map((team) => `- ${team.name} (${team.key}): ${(team.description || "No description").slice(0, MAX_DESCRIPTION_CHARS)}`);
2681
3136
  const text = `Linear Teams:
2682
3137
  ${teamsList.join(`
2683
3138
  `)}`;
2684
3139
  return {
2685
3140
  text,
2686
3141
  data: {
2687
- teams: teams.map((team) => ({
3142
+ teams: listedTeams.map((team) => ({
2688
3143
  id: team.id,
2689
3144
  name: team.name,
2690
3145
  key: team.key
2691
- }))
3146
+ })),
3147
+ truncated: teams.length > listedTeams.length
2692
3148
  }
2693
3149
  };
2694
3150
  } catch (_error) {
@@ -2699,8 +3155,85 @@ ${teamsList.join(`
2699
3155
  }
2700
3156
  };
2701
3157
 
3158
+ // src/search-category.ts
3159
+ var LINEAR_ISSUES_SEARCH_CATEGORY = {
3160
+ category: "linear_issues",
3161
+ label: "Linear issues",
3162
+ description: "Search Linear issues by text and issue metadata filters.",
3163
+ contexts: ["automation", "system"],
3164
+ filters: [
3165
+ { name: "query", label: "Query", type: "string", required: true },
3166
+ {
3167
+ name: "state",
3168
+ label: "States",
3169
+ description: "Issue workflow state names.",
3170
+ type: "string[]"
3171
+ },
3172
+ {
3173
+ name: "assignee",
3174
+ label: "Assignees",
3175
+ description: "Assignee names or emails.",
3176
+ type: "string[]"
3177
+ },
3178
+ {
3179
+ name: "label",
3180
+ label: "Labels",
3181
+ description: "Linear issue label names.",
3182
+ type: "string[]"
3183
+ },
3184
+ {
3185
+ name: "project",
3186
+ label: "Project",
3187
+ description: "Linear project name or identifier.",
3188
+ type: "string"
3189
+ },
3190
+ {
3191
+ name: "team",
3192
+ label: "Team",
3193
+ description: "Linear team key, name, or identifier.",
3194
+ type: "string"
3195
+ },
3196
+ {
3197
+ name: "priority",
3198
+ label: "Priorities",
3199
+ description: "Linear priorities: 1 urgent, 2 high, 3 normal, 4 low.",
3200
+ type: "number[]"
3201
+ },
3202
+ {
3203
+ name: "limit",
3204
+ label: "Limit",
3205
+ description: "Maximum issues to return.",
3206
+ type: "number",
3207
+ default: 10
3208
+ },
3209
+ {
3210
+ name: "accountId",
3211
+ label: "Account",
3212
+ description: "Optional Linear account id. Defaults to LINEAR_DEFAULT_ACCOUNT_ID or the legacy single API key.",
3213
+ type: "string"
3214
+ }
3215
+ ],
3216
+ resultSchemaSummary: "LinearIssue[] with id, identifier, title, description, state, assignee, labels, priority, team, project, url, and updatedAt.",
3217
+ capabilities: ["issues", "filters", "workflow", "team"],
3218
+ source: "plugin:linear",
3219
+ serviceType: "linear"
3220
+ };
3221
+ function hasSearchCategory(runtime, category) {
3222
+ try {
3223
+ runtime.getSearchCategory(category, { includeDisabled: true });
3224
+ return true;
3225
+ } catch {
3226
+ return false;
3227
+ }
3228
+ }
3229
+ function registerLinearSearchCategory(runtime) {
3230
+ if (!hasSearchCategory(runtime, LINEAR_ISSUES_SEARCH_CATEGORY.category)) {
3231
+ runtime.registerSearchCategory(LINEAR_ISSUES_SEARCH_CATEGORY);
3232
+ }
3233
+ }
3234
+
2702
3235
  // src/services/linear.ts
2703
- import { logger as logger11, Service } from "@elizaos/core";
3236
+ import { logger as logger13, Service } from "@elizaos/core";
2704
3237
  import {
2705
3238
  LinearClient
2706
3239
  } from "@linear/sdk";
@@ -2728,44 +3261,64 @@ class LinearAuthenticationError extends LinearAPIError {
2728
3261
  class LinearService extends Service {
2729
3262
  static serviceType = "linear";
2730
3263
  capabilityDescription = "Linear API integration for issue tracking, project management, and team collaboration";
2731
- client;
3264
+ clients = new Map;
2732
3265
  activityLog = [];
2733
- linearConfig;
3266
+ defaultAccountId = DEFAULT_LINEAR_ACCOUNT_ID;
2734
3267
  workspaceId;
2735
3268
  constructor(runtime) {
2736
3269
  super(runtime);
2737
- const apiKey = runtime?.getSetting("LINEAR_API_KEY");
2738
- const workspaceId = runtime?.getSetting("LINEAR_WORKSPACE_ID");
2739
- if (!apiKey) {
3270
+ const accounts = runtime ? readLinearAccounts(runtime) : [];
3271
+ const requestedDefault = runtime ? normalizeLinearAccountId(runtime.getSetting("LINEAR_DEFAULT_ACCOUNT_ID") ?? runtime.getSetting("LINEAR_ACCOUNT_ID")) : DEFAULT_LINEAR_ACCOUNT_ID;
3272
+ const defaultAccount = resolveLinearDefaultAccount(accounts, requestedDefault);
3273
+ if (!defaultAccount) {
2740
3274
  throw new LinearAuthenticationError("Linear API key is required");
2741
3275
  }
2742
- this.linearConfig = {
2743
- LINEAR_API_KEY: apiKey,
2744
- LINEAR_WORKSPACE_ID: workspaceId
2745
- };
2746
- this.workspaceId = workspaceId;
2747
- this.client = new LinearClient({
2748
- apiKey: this.linearConfig.LINEAR_API_KEY
2749
- });
3276
+ this.defaultAccountId = defaultAccount.accountId;
3277
+ this.workspaceId = defaultAccount.workspaceId;
3278
+ for (const account of accounts) {
3279
+ this.clients.set(account.accountId, {
3280
+ accountId: account.accountId,
3281
+ config: account,
3282
+ client: new LinearClient({ apiKey: account.apiKey })
3283
+ });
3284
+ }
2750
3285
  }
2751
3286
  static async start(runtime) {
2752
3287
  const service = new LinearService(runtime);
2753
3288
  await service.validateConnection();
2754
- logger11.info("Linear service started successfully");
3289
+ logger13.info("Linear service started successfully");
2755
3290
  return service;
2756
3291
  }
2757
3292
  async stop() {
2758
3293
  this.activityLog = [];
2759
- logger11.info("Linear service stopped");
3294
+ logger13.info("Linear service stopped");
2760
3295
  }
2761
- async validateConnection() {
3296
+ async validateConnection(accountId) {
2762
3297
  try {
2763
- const viewer = await this.client.viewer;
2764
- logger11.info(`Linear connected as user: ${viewer.email}`);
3298
+ const state = this.getAccountState(accountId);
3299
+ const viewer = await state.client.viewer;
3300
+ logger13.info(`Linear connected as user: ${viewer.email} (accountId=${state.accountId})`);
2765
3301
  } catch (_error) {
2766
3302
  throw new LinearAuthenticationError("Failed to authenticate with Linear API");
2767
3303
  }
2768
3304
  }
3305
+ hasAccount(accountId) {
3306
+ return Boolean(this.getAccountState(accountId, false));
3307
+ }
3308
+ getDefaultTeamKey(accountId) {
3309
+ return this.getAccountState(accountId).config.defaultTeamKey;
3310
+ }
3311
+ getAccountState(accountId, throwOnMissing = true) {
3312
+ const normalized = normalizeLinearAccountId(accountId);
3313
+ const state = accountId ? this.clients.get(normalized) ?? null : this.clients.get(this.defaultAccountId) ?? Array.from(this.clients.values())[0] ?? null;
3314
+ if (!state && throwOnMissing) {
3315
+ throw new LinearAuthenticationError("Linear API key is required");
3316
+ }
3317
+ return state;
3318
+ }
3319
+ getClient(accountId) {
3320
+ return this.getAccountState(accountId)?.client;
3321
+ }
2769
3322
  logActivity(action, resourceType, resourceId, details, success, error) {
2770
3323
  const activity = {
2771
3324
  id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
@@ -2782,7 +3335,7 @@ class LinearService extends Service {
2782
3335
  this.activityLog = this.activityLog.slice(-1000);
2783
3336
  }
2784
3337
  }
2785
- getActivityLog(limit, filter) {
3338
+ getActivityLog(limit, filter, accountId) {
2786
3339
  let filtered = [...this.activityLog];
2787
3340
  if (filter) {
2788
3341
  filtered = filtered.filter((item) => {
@@ -2791,25 +3344,36 @@ class LinearService extends Service {
2791
3344
  });
2792
3345
  });
2793
3346
  }
3347
+ if (accountId) {
3348
+ filtered = filtered.filter((item) => item.details.accountId === accountId);
3349
+ }
2794
3350
  return filtered.slice(-(limit || 100));
2795
3351
  }
2796
- clearActivityLog() {
3352
+ clearActivityLog(accountId) {
3353
+ if (accountId) {
3354
+ this.activityLog = this.activityLog.filter((item) => item.details.accountId !== accountId);
3355
+ logger13.info(`Linear activity log cleared for accountId=${accountId}`);
3356
+ return;
3357
+ }
2797
3358
  this.activityLog = [];
2798
- logger11.info("Linear activity log cleared");
3359
+ logger13.info("Linear activity log cleared");
2799
3360
  }
2800
- async getTeams() {
2801
- const teams = await this.client.teams();
3361
+ async getTeams(accountId) {
3362
+ const state = this.getAccountState(accountId);
3363
+ const teams = await state.client.teams();
2802
3364
  const teamList = await teams.nodes;
2803
- this.logActivity("list_teams", "team", "all", { count: teamList.length }, true);
3365
+ this.logActivity("list_teams", "team", "all", { count: teamList.length, accountId: state.accountId }, true);
2804
3366
  return teamList;
2805
3367
  }
2806
- async getTeam(teamId) {
2807
- const team = await this.client.team(teamId);
2808
- this.logActivity("get_team", "team", teamId, { name: team.name }, true);
3368
+ async getTeam(teamId, accountId) {
3369
+ const state = this.getAccountState(accountId);
3370
+ const team = await state.client.team(teamId);
3371
+ this.logActivity("get_team", "team", teamId, { name: team.name, accountId: state.accountId }, true);
2809
3372
  return team;
2810
3373
  }
2811
- async createIssue(input) {
2812
- const issuePayload = await this.client.createIssue({
3374
+ async createIssue(input, accountId) {
3375
+ const state = this.getAccountState(accountId);
3376
+ const issuePayload = await state.client.createIssue({
2813
3377
  title: input.title,
2814
3378
  description: input.description,
2815
3379
  teamId: input.teamId,
@@ -2827,20 +3391,24 @@ class LinearService extends Service {
2827
3391
  }
2828
3392
  this.logActivity("create_issue", "issue", issue.id, {
2829
3393
  title: input.title,
2830
- teamId: input.teamId
3394
+ teamId: input.teamId,
3395
+ accountId: state.accountId
2831
3396
  }, true);
2832
3397
  return issue;
2833
3398
  }
2834
- async getIssue(issueId) {
2835
- const issue = await this.client.issue(issueId);
3399
+ async getIssue(issueId, accountId) {
3400
+ const state = this.getAccountState(accountId);
3401
+ const issue = await state.client.issue(issueId);
2836
3402
  this.logActivity("get_issue", "issue", issueId, {
2837
3403
  title: issue.title,
2838
- identifier: issue.identifier
3404
+ identifier: issue.identifier,
3405
+ accountId: state.accountId
2839
3406
  }, true);
2840
3407
  return issue;
2841
3408
  }
2842
- async updateIssue(issueId, updates) {
2843
- const updatePayload = await this.client.updateIssue(issueId, {
3409
+ async updateIssue(issueId, updates, accountId) {
3410
+ const state = this.getAccountState(accountId);
3411
+ const updatePayload = await state.client.updateIssue(issueId, {
2844
3412
  title: updates.title,
2845
3413
  description: updates.description,
2846
3414
  priority: updates.priority,
@@ -2855,18 +3423,20 @@ class LinearService extends Service {
2855
3423
  if (!issue) {
2856
3424
  throw new Error("Failed to update issue");
2857
3425
  }
2858
- this.logActivity("update_issue", "issue", issueId, updates, true);
3426
+ this.logActivity("update_issue", "issue", issueId, { ...updates, accountId: state.accountId }, true);
2859
3427
  return issue;
2860
3428
  }
2861
- async deleteIssue(issueId) {
2862
- const archivePayload = await this.client.archiveIssue(issueId);
3429
+ async deleteIssue(issueId, accountId) {
3430
+ const state = this.getAccountState(accountId);
3431
+ const archivePayload = await state.client.archiveIssue(issueId);
2863
3432
  const success = await archivePayload.success;
2864
3433
  if (!success) {
2865
3434
  throw new Error("Failed to archive issue");
2866
3435
  }
2867
- this.logActivity("delete_issue", "issue", issueId, { action: "archived" }, true);
3436
+ this.logActivity("delete_issue", "issue", issueId, { action: "archived", accountId: state.accountId }, true);
2868
3437
  }
2869
- async searchIssues(filters) {
3438
+ async searchIssues(filters, accountId) {
3439
+ const state = this.getAccountState(accountId);
2870
3440
  const filterObject = {};
2871
3441
  if (filters.query) {
2872
3442
  filterObject.or = [
@@ -2875,14 +3445,14 @@ class LinearService extends Service {
2875
3445
  ];
2876
3446
  }
2877
3447
  if (filters.team) {
2878
- const teams = await this.getTeams();
3448
+ const teams = await this.getTeams(state.accountId);
2879
3449
  const team = teams.find((t) => t.key.toLowerCase() === filters.team?.toLowerCase() || t.name.toLowerCase() === filters.team?.toLowerCase());
2880
3450
  if (team) {
2881
3451
  filterObject.team = { id: { eq: team.id } };
2882
3452
  }
2883
3453
  }
2884
3454
  if (filters.assignee && filters.assignee.length > 0) {
2885
- const users = await this.getUsers();
3455
+ const users = await this.getUsers(state.accountId);
2886
3456
  const assigneeIds = filters.assignee.map((assigneeName) => {
2887
3457
  const user = users.find((u) => u.email === assigneeName || u.name.toLowerCase().includes(assigneeName.toLowerCase()));
2888
3458
  return user?.id;
@@ -2906,20 +3476,21 @@ class LinearService extends Service {
2906
3476
  }
2907
3477
  };
2908
3478
  }
2909
- const query = this.client.issues({
3479
+ const query = state.client.issues({
2910
3480
  first: filters.limit || 50,
2911
3481
  filter: Object.keys(filterObject).length > 0 ? filterObject : undefined
2912
3482
  });
2913
3483
  const issues = await query;
2914
3484
  const issueList = await issues.nodes;
2915
3485
  this.logActivity("search_issues", "issue", "search", {
2916
- filters: { ...filters },
3486
+ filters: { ...filters, accountId: state.accountId },
2917
3487
  count: issueList.length
2918
3488
  }, true);
2919
3489
  return issueList;
2920
3490
  }
2921
- async createComment(input) {
2922
- const commentPayload = await this.client.createComment({
3491
+ async createComment(input, accountId) {
3492
+ const state = this.getAccountState(accountId);
3493
+ const commentPayload = await state.client.createComment({
2923
3494
  body: input.body,
2924
3495
  issueId: input.issueId
2925
3496
  });
@@ -2929,12 +3500,39 @@ class LinearService extends Service {
2929
3500
  }
2930
3501
  this.logActivity("create_comment", "comment", comment.id, {
2931
3502
  issueId: input.issueId,
2932
- bodyLength: input.body.length
3503
+ bodyLength: input.body.length,
3504
+ accountId: state.accountId
2933
3505
  }, true);
2934
3506
  return comment;
2935
3507
  }
2936
- async getProjects(teamId) {
2937
- const query = this.client.projects({
3508
+ async updateComment(commentId, body, accountId) {
3509
+ const state = this.getAccountState(accountId);
3510
+ const commentPayload = await state.client.updateComment(commentId, {
3511
+ body
3512
+ });
3513
+ const comment = await commentPayload.comment;
3514
+ if (!comment) {
3515
+ throw new Error("Failed to update comment");
3516
+ }
3517
+ this.logActivity("update_comment", "comment", commentId, { bodyLength: body.length, accountId: state.accountId }, true);
3518
+ return comment;
3519
+ }
3520
+ async deleteComment(commentId, accountId) {
3521
+ const state = this.getAccountState(accountId);
3522
+ const payload = await state.client.deleteComment(commentId);
3523
+ if (!payload.success) {
3524
+ throw new Error("Failed to delete comment");
3525
+ }
3526
+ this.logActivity("delete_comment", "comment", commentId, { accountId: state.accountId }, true);
3527
+ }
3528
+ async listComments(issueId, limit = 25, accountId) {
3529
+ const issue = await this.getClient(accountId).issue(issueId);
3530
+ const connection = await issue.comments({ first: Math.min(limit, 100) });
3531
+ return connection.nodes;
3532
+ }
3533
+ async getProjects(teamId, accountId) {
3534
+ const state = this.getAccountState(accountId);
3535
+ const query = state.client.projects({
2938
3536
  first: 100
2939
3537
  });
2940
3538
  const projects = await query;
@@ -2950,44 +3548,54 @@ class LinearService extends Service {
2950
3548
  }
2951
3549
  this.logActivity("list_projects", "project", "all", {
2952
3550
  count: projectList.length,
2953
- teamId
3551
+ teamId,
3552
+ accountId: state.accountId
2954
3553
  }, true);
2955
3554
  return projectList;
2956
3555
  }
2957
- async getProject(projectId) {
2958
- const project = await this.client.project(projectId);
3556
+ async getProject(projectId, accountId) {
3557
+ const state = this.getAccountState(accountId);
3558
+ const project = await state.client.project(projectId);
2959
3559
  this.logActivity("get_project", "project", projectId, {
2960
- name: project.name
3560
+ name: project.name,
3561
+ accountId: state.accountId
2961
3562
  }, true);
2962
3563
  return project;
2963
3564
  }
2964
- async getUsers() {
2965
- const users = await this.client.users();
3565
+ async getUsers(accountId) {
3566
+ const state = this.getAccountState(accountId);
3567
+ const users = await state.client.users();
2966
3568
  const userList = await users.nodes;
2967
3569
  this.logActivity("list_users", "user", "all", {
2968
- count: userList.length
3570
+ count: userList.length,
3571
+ accountId: state.accountId
2969
3572
  }, true);
2970
3573
  return userList;
2971
3574
  }
2972
- async getCurrentUser() {
2973
- const user = await this.client.viewer;
3575
+ async getCurrentUser(accountId) {
3576
+ const state = this.getAccountState(accountId);
3577
+ const user = await state.client.viewer;
2974
3578
  this.logActivity("get_current_user", "user", user.id, {
2975
3579
  email: user.email,
2976
- name: user.name
3580
+ name: user.name,
3581
+ accountId: state.accountId
2977
3582
  }, true);
2978
3583
  return user;
2979
3584
  }
2980
- async getUserTeams() {
2981
- const viewer = await this.client.viewer;
3585
+ async getUserTeams(accountId) {
3586
+ const state = this.getAccountState(accountId);
3587
+ const viewer = await state.client.viewer;
2982
3588
  const teams = await viewer.teams();
2983
3589
  const teamList = await teams.nodes;
2984
3590
  this.logActivity("list_user_teams", "team", viewer.id, {
2985
- count: teamList.length
3591
+ count: teamList.length,
3592
+ accountId: state.accountId
2986
3593
  }, true);
2987
3594
  return teamList;
2988
3595
  }
2989
- async getLabels(teamId) {
2990
- const query = this.client.issueLabels({
3596
+ async getLabels(teamId, accountId) {
3597
+ const state = this.getAccountState(accountId);
3598
+ const query = state.client.issueLabels({
2991
3599
  first: 100,
2992
3600
  filter: teamId ? {
2993
3601
  team: { id: { eq: teamId } }
@@ -2997,19 +3605,22 @@ class LinearService extends Service {
2997
3605
  const labelList = await labels.nodes;
2998
3606
  this.logActivity("list_labels", "label", "all", {
2999
3607
  count: labelList.length,
3000
- teamId
3608
+ teamId,
3609
+ accountId: state.accountId
3001
3610
  }, true);
3002
3611
  return labelList;
3003
3612
  }
3004
- async getWorkflowStates(teamId) {
3005
- const states = await this.client.workflowStates({
3613
+ async getWorkflowStates(teamId, accountId) {
3614
+ const state = this.getAccountState(accountId);
3615
+ const states = await state.client.workflowStates({
3006
3616
  filter: {
3007
3617
  team: { id: { eq: teamId } }
3008
3618
  }
3009
3619
  });
3010
3620
  const stateList = await states.nodes;
3011
3621
  this.logActivity("list_workflow_states", "team", teamId, {
3012
- count: stateList.length
3622
+ count: stateList.length,
3623
+ accountId: state.accountId
3013
3624
  }, true);
3014
3625
  return stateList;
3015
3626
  }
@@ -3020,28 +3631,38 @@ var linearPlugin = {
3020
3631
  name: "@elizaos/plugin-linear-ts",
3021
3632
  description: "Plugin for integrating with Linear issue tracking system",
3022
3633
  services: [LinearService],
3023
- actions: [
3024
- createIssueAction,
3025
- getIssueAction,
3026
- updateIssueAction,
3027
- deleteIssueAction,
3028
- searchIssuesAction,
3029
- createCommentAction,
3030
- listTeamsAction,
3031
- listProjectsAction,
3032
- getActivityAction,
3033
- clearActivityAction
3034
- ],
3634
+ actions: [...promoteSubactionsToActions(linearAction)],
3035
3635
  providers: [
3036
3636
  linearIssuesProvider,
3037
3637
  linearTeamsProvider,
3038
3638
  linearProjectsProvider,
3039
3639
  linearActivityProvider
3040
- ]
3640
+ ],
3641
+ init: async (_config, runtime) => {
3642
+ registerLinearSearchCategory(runtime);
3643
+ try {
3644
+ const manager = getConnectorAccountManager(runtime);
3645
+ manager.registerProvider(createLinearConnectorAccountProvider(runtime));
3646
+ } catch (err) {
3647
+ logger14.warn({
3648
+ src: "plugin:linear",
3649
+ err: err instanceof Error ? err.message : String(err)
3650
+ }, "Failed to register Linear provider with ConnectorAccountManager");
3651
+ }
3652
+ }
3041
3653
  };
3042
3654
  export {
3655
+ resolveLinearDefaultAccount,
3656
+ resolveLinearAccountId,
3657
+ resolveLinearAccount,
3658
+ readLinearAccounts,
3659
+ normalizeLinearAccountId,
3043
3660
  linearPlugin,
3044
- LinearService
3661
+ hasLinearAccountConfig,
3662
+ createLinearConnectorAccountProvider,
3663
+ LinearService,
3664
+ DEFAULT_LINEAR_ACCOUNT_ROLE,
3665
+ DEFAULT_LINEAR_ACCOUNT_ID
3045
3666
  };
3046
3667
 
3047
- //# debugId=FD61149701EB826464756E2164756E21
3668
+ //# debugId=F5689DC5E2BC1C6664756E2164756E21