cyrus-edge-worker 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AgentSessionManager.d.ts +19 -5
- package/dist/AgentSessionManager.d.ts.map +1 -1
- package/dist/AgentSessionManager.js +353 -39
- package/dist/AgentSessionManager.js.map +1 -1
- package/dist/EdgeWorker.d.ts +4 -4
- package/dist/EdgeWorker.d.ts.map +1 -1
- package/dist/EdgeWorker.js +155 -141
- package/dist/EdgeWorker.js.map +1 -1
- package/dist/RepositoryRouter.d.ts +5 -6
- package/dist/RepositoryRouter.d.ts.map +1 -1
- package/dist/RepositoryRouter.js +17 -13
- package/dist/RepositoryRouter.js.map +1 -1
- package/dist/prompt-assembly/types.d.ts +5 -6
- package/dist/prompt-assembly/types.d.ts.map +1 -1
- package/dist/types.d.ts +5 -6
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -8
package/dist/EdgeWorker.js
CHANGED
|
@@ -2,12 +2,12 @@ import { EventEmitter } from "node:events";
|
|
|
2
2
|
import { mkdir, readdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { LinearClient
|
|
5
|
+
import { LinearClient } from "@linear/sdk";
|
|
6
6
|
import { watch as chokidarWatch } from "chokidar";
|
|
7
7
|
import { AbortError, ClaudeRunner, createCyrusToolsServer, createImageToolsServer, createSoraToolsServer, getAllTools, getCoordinatorTools, getReadOnlyTools, getSafeTools, } from "cyrus-claude-runner";
|
|
8
8
|
import { ConfigUpdater } from "cyrus-config-updater";
|
|
9
9
|
import { DEFAULT_PROXY_URL, isAgentSessionCreatedWebhook, isAgentSessionPromptedWebhook, isIssueAssignedWebhook, isIssueCommentMentionWebhook, isIssueNewCommentWebhook, isIssueUnassignedWebhook, PersistenceManager, resolvePath, } from "cyrus-core";
|
|
10
|
-
import { LinearEventTransport } from "cyrus-linear-event-transport";
|
|
10
|
+
import { LinearEventTransport, LinearIssueTrackerService, } from "cyrus-linear-event-transport";
|
|
11
11
|
import { fileTypeFromBuffer } from "file-type";
|
|
12
12
|
import { AgentSessionManager } from "./AgentSessionManager.js";
|
|
13
13
|
import { ProcedureRouter, } from "./procedures/index.js";
|
|
@@ -23,7 +23,7 @@ export class EdgeWorker extends EventEmitter {
|
|
|
23
23
|
config;
|
|
24
24
|
repositories = new Map(); // repository 'id' (internal, stored in config.json) mapped to the full repo config
|
|
25
25
|
agentSessionManagers = new Map(); // Maps repository ID to AgentSessionManager, which manages ClaudeRunners for a repo
|
|
26
|
-
|
|
26
|
+
issueTrackers = new Map(); // one issue tracker per 'repository'
|
|
27
27
|
linearEventTransport = null; // Single event transport for webhook delivery
|
|
28
28
|
configUpdater = null; // Single config updater for configuration updates
|
|
29
29
|
persistenceManager;
|
|
@@ -49,20 +49,16 @@ export class EdgeWorker extends EventEmitter {
|
|
|
49
49
|
// Initialize repository router with dependencies
|
|
50
50
|
const repositoryRouterDeps = {
|
|
51
51
|
fetchIssueLabels: async (issueId, workspaceId) => {
|
|
52
|
-
//
|
|
53
|
-
const
|
|
54
|
-
if (!
|
|
55
|
-
console.warn(`[EdgeWorker] No Linear client found for workspace ${workspaceId}`);
|
|
52
|
+
// Find repository for this workspace
|
|
53
|
+
const repo = Array.from(this.repositories.values()).find((r) => r.linearWorkspaceId === workspaceId);
|
|
54
|
+
if (!repo)
|
|
56
55
|
return [];
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return await this.fetchIssueLabels(issue);
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
console.error(`[EdgeWorker] Failed to fetch issue labels for ${issueId}:`, error);
|
|
56
|
+
// Get issue tracker for this repository
|
|
57
|
+
const issueTracker = this.issueTrackers.get(repo.id);
|
|
58
|
+
if (!issueTracker)
|
|
64
59
|
return [];
|
|
65
|
-
|
|
60
|
+
// Use platform-agnostic getIssueLabels method
|
|
61
|
+
return await issueTracker.getIssueLabels(issueId);
|
|
66
62
|
},
|
|
67
63
|
hasActiveSession: (issueId, repositoryId) => {
|
|
68
64
|
const sessionManager = this.agentSessionManagers.get(repositoryId);
|
|
@@ -71,8 +67,8 @@ export class EdgeWorker extends EventEmitter {
|
|
|
71
67
|
const activeSessions = sessionManager.getActiveSessionsByIssueId(issueId);
|
|
72
68
|
return activeSessions.length > 0;
|
|
73
69
|
},
|
|
74
|
-
|
|
75
|
-
return this.
|
|
70
|
+
getIssueTracker: (workspaceId) => {
|
|
71
|
+
return this.getIssueTrackerForWorkspace(workspaceId);
|
|
76
72
|
},
|
|
77
73
|
};
|
|
78
74
|
this.repositoryRouter = new RepositoryRouter(repositoryRouterDeps);
|
|
@@ -103,11 +99,12 @@ export class EdgeWorker extends EventEmitter {
|
|
|
103
99
|
: undefined,
|
|
104
100
|
};
|
|
105
101
|
this.repositories.set(repo.id, resolvedRepo);
|
|
106
|
-
// Create
|
|
102
|
+
// Create issue tracker for this repository's workspace
|
|
107
103
|
const linearClient = new LinearClient({
|
|
108
104
|
accessToken: repo.linearToken,
|
|
109
105
|
});
|
|
110
|
-
|
|
106
|
+
const issueTracker = new LinearIssueTrackerService(linearClient);
|
|
107
|
+
this.issueTrackers.set(repo.id, issueTracker);
|
|
111
108
|
// Create AgentSessionManager for this repository with parent session lookup and resume callback
|
|
112
109
|
//
|
|
113
110
|
// Note: This pattern works (despite appearing recursive) because:
|
|
@@ -118,7 +115,7 @@ export class EdgeWorker extends EventEmitter {
|
|
|
118
115
|
//
|
|
119
116
|
// This allows the AgentSessionManager to call back into itself to access its own sessions,
|
|
120
117
|
// enabling child sessions to trigger parent session resumption using the same manager instance.
|
|
121
|
-
const agentSessionManager = new AgentSessionManager(
|
|
118
|
+
const agentSessionManager = new AgentSessionManager(issueTracker, (childSessionId) => {
|
|
122
119
|
console.log(`[Parent-Child Lookup] Looking up parent session for child ${childSessionId}`);
|
|
123
120
|
const parentId = this.childToParentAgentSession.get(childSessionId);
|
|
124
121
|
console.log(`[Parent-Child Lookup] Child ${childSessionId} -> Parent ${parentId || "not found"}`);
|
|
@@ -208,10 +205,10 @@ export class EdgeWorker extends EventEmitter {
|
|
|
208
205
|
secret,
|
|
209
206
|
});
|
|
210
207
|
// Listen for webhook events
|
|
211
|
-
this.linearEventTransport.on("
|
|
208
|
+
this.linearEventTransport.on("event", (event) => {
|
|
212
209
|
// Get all active repositories for webhook handling
|
|
213
210
|
const repos = Array.from(this.repositories.values());
|
|
214
|
-
this.handleWebhook(
|
|
211
|
+
this.handleWebhook(event, repos);
|
|
215
212
|
});
|
|
216
213
|
// Listen for errors
|
|
217
214
|
this.linearEventTransport.on("error", (error) => {
|
|
@@ -301,12 +298,12 @@ export class EdgeWorker extends EventEmitter {
|
|
|
301
298
|
}
|
|
302
299
|
await this.postParentResumeAcknowledgment(parentSessionId, repo.id);
|
|
303
300
|
// Post thought to Linear showing child result receipt
|
|
304
|
-
const
|
|
305
|
-
if (
|
|
301
|
+
const issueTracker = this.issueTrackers.get(repo.id);
|
|
302
|
+
if (issueTracker && childSession) {
|
|
306
303
|
const childIssueIdentifier = childSession.issue?.identifier || childSession.issueId;
|
|
307
304
|
const resultThought = `Received result from sub-issue ${childIssueIdentifier}:\n\n---\n\n${prompt}\n\n---`;
|
|
308
305
|
try {
|
|
309
|
-
const result = await
|
|
306
|
+
const result = await issueTracker.createAgentActivity({
|
|
310
307
|
agentSessionId: parentSessionId,
|
|
311
308
|
content: {
|
|
312
309
|
type: "thought",
|
|
@@ -502,13 +499,14 @@ export class EdgeWorker extends EventEmitter {
|
|
|
502
499
|
};
|
|
503
500
|
// Add to internal map
|
|
504
501
|
this.repositories.set(repo.id, resolvedRepo);
|
|
505
|
-
// Create
|
|
502
|
+
// Create issue tracker
|
|
506
503
|
const linearClient = new LinearClient({
|
|
507
504
|
accessToken: repo.linearToken,
|
|
508
505
|
});
|
|
509
|
-
|
|
506
|
+
const issueTracker = new LinearIssueTrackerService(linearClient);
|
|
507
|
+
this.issueTrackers.set(repo.id, issueTracker);
|
|
510
508
|
// Create AgentSessionManager with same pattern as constructor
|
|
511
|
-
const agentSessionManager = new AgentSessionManager(
|
|
509
|
+
const agentSessionManager = new AgentSessionManager(issueTracker, (childSessionId) => {
|
|
512
510
|
return this.childToParentAgentSession.get(childSessionId);
|
|
513
511
|
}, async (parentSessionId, prompt, childSessionId) => {
|
|
514
512
|
await this.handleResumeParentSession(parentSessionId, prompt, childSessionId, repo, agentSessionManager);
|
|
@@ -553,13 +551,14 @@ export class EdgeWorker extends EventEmitter {
|
|
|
553
551
|
};
|
|
554
552
|
// Update stored config
|
|
555
553
|
this.repositories.set(repo.id, resolvedRepo);
|
|
556
|
-
// If token changed, recreate
|
|
554
|
+
// If token changed, recreate issue tracker
|
|
557
555
|
if (oldRepo.linearToken !== repo.linearToken) {
|
|
558
|
-
console.log(` 🔑 Token changed, recreating
|
|
556
|
+
console.log(` 🔑 Token changed, recreating issue tracker`);
|
|
559
557
|
const linearClient = new LinearClient({
|
|
560
558
|
accessToken: repo.linearToken,
|
|
561
559
|
});
|
|
562
|
-
|
|
560
|
+
const issueTracker = new LinearIssueTrackerService(linearClient);
|
|
561
|
+
this.issueTrackers.set(repo.id, issueTracker);
|
|
563
562
|
}
|
|
564
563
|
// If active status changed
|
|
565
564
|
if (oldRepo.isActive !== repo.isActive) {
|
|
@@ -601,9 +600,9 @@ export class EdgeWorker extends EventEmitter {
|
|
|
601
600
|
console.log(` ✅ Stopped Claude runner for session ${session.linearAgentActivitySessionId}`);
|
|
602
601
|
}
|
|
603
602
|
// Post cancellation message to Linear
|
|
604
|
-
const
|
|
605
|
-
if (
|
|
606
|
-
await
|
|
603
|
+
const issueTracker = this.issueTrackers.get(repo.id);
|
|
604
|
+
if (issueTracker) {
|
|
605
|
+
await issueTracker.createAgentActivity({
|
|
607
606
|
agentSessionId: session.linearAgentActivitySessionId,
|
|
608
607
|
content: {
|
|
609
608
|
type: "response",
|
|
@@ -620,7 +619,7 @@ export class EdgeWorker extends EventEmitter {
|
|
|
620
619
|
}
|
|
621
620
|
// Remove repository from all maps
|
|
622
621
|
this.repositories.delete(repo.id);
|
|
623
|
-
this.
|
|
622
|
+
this.issueTrackers.delete(repo.id);
|
|
624
623
|
this.agentSessionManagers.delete(repo.id);
|
|
625
624
|
console.log(`✅ Repository removed successfully: ${repo.name}`);
|
|
626
625
|
}
|
|
@@ -688,6 +687,10 @@ export class EdgeWorker extends EventEmitter {
|
|
|
688
687
|
* Handle issue unassignment webhook
|
|
689
688
|
*/
|
|
690
689
|
async handleIssueUnassignedWebhook(webhook) {
|
|
690
|
+
if (!webhook.notification.issue) {
|
|
691
|
+
console.warn("[EdgeWorker] Received issue unassignment webhook without issue");
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
691
694
|
const issueId = webhook.notification.issue.id;
|
|
692
695
|
// Get cached repository (unassignment should only happen on issues with active sessions)
|
|
693
696
|
const repository = this.getCachedRepository(issueId);
|
|
@@ -703,12 +706,12 @@ export class EdgeWorker extends EventEmitter {
|
|
|
703
706
|
await this.handleIssueUnassigned(webhook.notification.issue, repository);
|
|
704
707
|
}
|
|
705
708
|
/**
|
|
706
|
-
* Get
|
|
709
|
+
* Get issue tracker for a workspace by finding first repository with that workspace ID
|
|
707
710
|
*/
|
|
708
|
-
|
|
711
|
+
getIssueTrackerForWorkspace(workspaceId) {
|
|
709
712
|
for (const [repoId, repo] of this.repositories) {
|
|
710
713
|
if (repo.linearWorkspaceId === workspaceId) {
|
|
711
|
-
return this.
|
|
714
|
+
return this.issueTrackers.get(repoId);
|
|
712
715
|
}
|
|
713
716
|
}
|
|
714
717
|
return undefined;
|
|
@@ -811,6 +814,10 @@ export class EdgeWorker extends EventEmitter {
|
|
|
811
814
|
// Post agent activity showing auto-matched routing
|
|
812
815
|
await this.postRepositorySelectionActivity(webhook.agentSession.id, repository.id, repository.name, routingMethod);
|
|
813
816
|
}
|
|
817
|
+
if (!webhook.agentSession.issue) {
|
|
818
|
+
console.warn("[EdgeWorker] Agent session created webhook missing issue");
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
814
821
|
console.log(`[EdgeWorker] Handling agent session created: ${webhook.agentSession.issue.identifier}`);
|
|
815
822
|
const { agentSession, guidance } = webhook;
|
|
816
823
|
const commentBody = agentSession.comment?.body;
|
|
@@ -832,6 +839,10 @@ export class EdgeWorker extends EventEmitter {
|
|
|
832
839
|
async initializeClaudeRunner(agentSession, repository, guidance, commentBody) {
|
|
833
840
|
const linearAgentActivitySessionId = agentSession.id;
|
|
834
841
|
const { issue } = agentSession;
|
|
842
|
+
if (!issue) {
|
|
843
|
+
console.warn("[EdgeWorker] Cannot initialize Claude runner without issue");
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
835
846
|
// Log guidance if present
|
|
836
847
|
if (guidance && guidance.length > 0) {
|
|
837
848
|
console.log(`[EdgeWorker] Agent guidance received: ${guidance.length} rule(s)`);
|
|
@@ -931,7 +942,7 @@ export class EdgeWorker extends EventEmitter {
|
|
|
931
942
|
repository,
|
|
932
943
|
userComment: commentBody || "", // Empty for delegation, present for mentions
|
|
933
944
|
attachmentManifest: attachmentResult.manifest,
|
|
934
|
-
guidance,
|
|
945
|
+
guidance: guidance || undefined,
|
|
935
946
|
agentSession,
|
|
936
947
|
labels,
|
|
937
948
|
isNewSession: true,
|
|
@@ -1027,7 +1038,7 @@ export class EdgeWorker extends EventEmitter {
|
|
|
1027
1038
|
console.log(`[EdgeWorker] Stopped Claude session for agent activity session ${agentSessionId}`);
|
|
1028
1039
|
}
|
|
1029
1040
|
// Post confirmation
|
|
1030
|
-
const issueTitle = issue
|
|
1041
|
+
const issueTitle = issue?.title || "this issue";
|
|
1031
1042
|
const stopConfirmation = `I've stopped working on ${issueTitle} as requested.\n\n**Stop Signal:** Received from ${webhook.agentSession.creator?.name || "user"}\n**Action Taken:** All ongoing work has been halted`;
|
|
1032
1043
|
await foundManager.createResponseActivity(agentSessionId, stopConfirmation);
|
|
1033
1044
|
}
|
|
@@ -1043,6 +1054,14 @@ export class EdgeWorker extends EventEmitter {
|
|
|
1043
1054
|
const { agentSession, agentActivity, guidance } = webhook;
|
|
1044
1055
|
const commentBody = agentSession.comment?.body;
|
|
1045
1056
|
const agentSessionId = agentSession.id;
|
|
1057
|
+
if (!agentActivity) {
|
|
1058
|
+
console.warn("[EdgeWorker] Cannot handle repository selection without agentActivity");
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
if (!agentSession.issue) {
|
|
1062
|
+
console.warn("[EdgeWorker] Cannot handle repository selection without issue");
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1046
1065
|
const userMessage = agentActivity.content.body;
|
|
1047
1066
|
console.log(`[EdgeWorker] Processing repository selection response: "${userMessage}"`);
|
|
1048
1067
|
// Get the selected repository (or fallback)
|
|
@@ -1068,6 +1087,14 @@ export class EdgeWorker extends EventEmitter {
|
|
|
1068
1087
|
const { agentSession } = webhook;
|
|
1069
1088
|
const linearAgentActivitySessionId = agentSession.id;
|
|
1070
1089
|
const { issue } = agentSession;
|
|
1090
|
+
if (!issue) {
|
|
1091
|
+
console.warn("[EdgeWorker] Cannot handle prompted activity without issue");
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
if (!webhook.agentActivity) {
|
|
1095
|
+
console.warn("[EdgeWorker] Cannot handle prompted activity without agentActivity");
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1071
1098
|
const commentId = webhook.agentActivity.sourceCommentId;
|
|
1072
1099
|
// Initialize the agent session in AgentSessionManager
|
|
1073
1100
|
const agentSessionManager = this.agentSessionManagers.get(repository.id);
|
|
@@ -1101,10 +1128,10 @@ export class EdgeWorker extends EventEmitter {
|
|
|
1101
1128
|
const isCurrentlyStreaming = session?.claudeRunner?.isStreaming() || false;
|
|
1102
1129
|
await this.postInstantPromptedAcknowledgment(linearAgentActivitySessionId, repository.id, isCurrentlyStreaming);
|
|
1103
1130
|
// Need to fetch full issue for routing context
|
|
1104
|
-
const
|
|
1105
|
-
if (
|
|
1131
|
+
const issueTracker = this.issueTrackers.get(repository.id);
|
|
1132
|
+
if (issueTracker) {
|
|
1106
1133
|
try {
|
|
1107
|
-
fullIssue = await
|
|
1134
|
+
fullIssue = await issueTracker.fetchIssue(issue.id);
|
|
1108
1135
|
}
|
|
1109
1136
|
catch (error) {
|
|
1110
1137
|
console.warn(`[EdgeWorker] Failed to fetch full issue for routing: ${issue.id}`, error);
|
|
@@ -1120,10 +1147,10 @@ export class EdgeWorker extends EventEmitter {
|
|
|
1120
1147
|
}
|
|
1121
1148
|
// Acknowledgment already posted above for both new and existing sessions
|
|
1122
1149
|
// (before any async routing work to ensure instant user feedback)
|
|
1123
|
-
// Get
|
|
1124
|
-
const
|
|
1125
|
-
if (!
|
|
1126
|
-
console.error("Unexpected: There was no
|
|
1150
|
+
// Get issue tracker for this repository
|
|
1151
|
+
const issueTracker = this.issueTrackers.get(repository.id);
|
|
1152
|
+
if (!issueTracker) {
|
|
1153
|
+
console.error("Unexpected: There was no IssueTrackerService for the repository with id", repository.id);
|
|
1127
1154
|
return;
|
|
1128
1155
|
}
|
|
1129
1156
|
// Always set up attachments directory, even if no attachments in current comment
|
|
@@ -1134,37 +1161,34 @@ export class EdgeWorker extends EventEmitter {
|
|
|
1134
1161
|
let attachmentManifest = "";
|
|
1135
1162
|
let commentAuthor;
|
|
1136
1163
|
let commentTimestamp;
|
|
1164
|
+
if (!commentId) {
|
|
1165
|
+
console.warn("[EdgeWorker] No comment ID provided for attachment handling");
|
|
1166
|
+
}
|
|
1137
1167
|
try {
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
id
|
|
1142
|
-
body
|
|
1143
|
-
createdAt
|
|
1144
|
-
updatedAt
|
|
1145
|
-
user {
|
|
1146
|
-
name
|
|
1147
|
-
displayName
|
|
1148
|
-
email
|
|
1149
|
-
id
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
`, { id: commentId });
|
|
1154
|
-
// Extract comment data
|
|
1155
|
-
const comment = result.data.comment;
|
|
1168
|
+
const comment = commentId
|
|
1169
|
+
? await issueTracker.fetchComment(commentId)
|
|
1170
|
+
: null;
|
|
1156
1171
|
// Extract comment metadata for multi-player context
|
|
1157
1172
|
if (comment) {
|
|
1158
|
-
const user = comment.user;
|
|
1173
|
+
const user = await comment.user;
|
|
1159
1174
|
commentAuthor =
|
|
1160
1175
|
user?.displayName || user?.name || user?.email || "Unknown";
|
|
1161
|
-
commentTimestamp = comment.createdAt
|
|
1176
|
+
commentTimestamp = comment.createdAt
|
|
1177
|
+
? comment.createdAt.toISOString()
|
|
1178
|
+
: new Date().toISOString();
|
|
1162
1179
|
}
|
|
1163
1180
|
// Count existing attachments
|
|
1164
1181
|
const existingFiles = await readdir(attachmentsDir).catch(() => []);
|
|
1165
1182
|
const existingAttachmentCount = existingFiles.filter((file) => file.startsWith("attachment_") || file.startsWith("image_")).length;
|
|
1166
1183
|
// Download new attachments from the comment
|
|
1167
|
-
const downloadResult =
|
|
1184
|
+
const downloadResult = comment
|
|
1185
|
+
? await this.downloadCommentAttachments(comment.body, attachmentsDir, repository.linearToken, existingAttachmentCount)
|
|
1186
|
+
: {
|
|
1187
|
+
totalNewAttachments: 0,
|
|
1188
|
+
newAttachmentMap: {},
|
|
1189
|
+
newImageMap: {},
|
|
1190
|
+
failedCount: 0,
|
|
1191
|
+
};
|
|
1168
1192
|
if (downloadResult.totalNewAttachments > 0) {
|
|
1169
1193
|
attachmentManifest = this.generateNewAttachmentManifest(downloadResult);
|
|
1170
1194
|
}
|
|
@@ -1196,7 +1220,7 @@ export class EdgeWorker extends EventEmitter {
|
|
|
1196
1220
|
// Branch 1: Handle stop signal (checked FIRST, before any routing work)
|
|
1197
1221
|
// Per CLAUDE.md: "an agentSession MUST already exist" for stop signals
|
|
1198
1222
|
// IMPORTANT: Stop signals do NOT require repository lookup
|
|
1199
|
-
if (webhook.agentActivity
|
|
1223
|
+
if (webhook.agentActivity?.signal === "stop") {
|
|
1200
1224
|
await this.handleStopSignal(webhook);
|
|
1201
1225
|
return;
|
|
1202
1226
|
}
|
|
@@ -1373,10 +1397,10 @@ export class EdgeWorker extends EventEmitter {
|
|
|
1373
1397
|
console.warn(`[EdgeWorker] Failed to fetch assignee details:`, error);
|
|
1374
1398
|
}
|
|
1375
1399
|
// Get LinearClient for this repository
|
|
1376
|
-
const
|
|
1377
|
-
if (!
|
|
1378
|
-
console.error(`No
|
|
1379
|
-
throw new Error(`No
|
|
1400
|
+
const issueTracker = this.issueTrackers.get(repository.id);
|
|
1401
|
+
if (!issueTracker) {
|
|
1402
|
+
console.error(`No IssueTrackerService found for repository ${repository.id}`);
|
|
1403
|
+
throw new Error(`No IssueTrackerService found for repository ${repository.id}`);
|
|
1380
1404
|
}
|
|
1381
1405
|
// Fetch workspace teams and labels
|
|
1382
1406
|
let workspaceTeams = "";
|
|
@@ -1384,7 +1408,7 @@ export class EdgeWorker extends EventEmitter {
|
|
|
1384
1408
|
try {
|
|
1385
1409
|
console.log(`[EdgeWorker] Fetching workspace teams and labels for repository ${repository.id}`);
|
|
1386
1410
|
// Fetch teams
|
|
1387
|
-
const teamsConnection = await
|
|
1411
|
+
const teamsConnection = await issueTracker.fetchTeams();
|
|
1388
1412
|
const teamsArray = [];
|
|
1389
1413
|
for (const team of teamsConnection.nodes) {
|
|
1390
1414
|
teamsArray.push({
|
|
@@ -1399,7 +1423,7 @@ export class EdgeWorker extends EventEmitter {
|
|
|
1399
1423
|
.map((team) => `- ${team.name} (${team.key}): ${team.id}${team.description ? ` - ${team.description}` : ""}`)
|
|
1400
1424
|
.join("\n");
|
|
1401
1425
|
// Fetch labels
|
|
1402
|
-
const labelsConnection = await
|
|
1426
|
+
const labelsConnection = await issueTracker.fetchLabels();
|
|
1403
1427
|
const labelsArray = [];
|
|
1404
1428
|
for (const label of labelsConnection.nodes) {
|
|
1405
1429
|
labelsArray.push({
|
|
@@ -1721,14 +1745,12 @@ ${reply.body}
|
|
|
1721
1745
|
// Determine the base branch considering parent issues
|
|
1722
1746
|
const baseBranch = await this.determineBaseBranch(issue, repository);
|
|
1723
1747
|
// Get formatted comment threads
|
|
1724
|
-
const
|
|
1748
|
+
const issueTracker = this.issueTrackers.get(repository.id);
|
|
1725
1749
|
let commentThreads = "No comments yet.";
|
|
1726
|
-
if (
|
|
1750
|
+
if (issueTracker && issue.id) {
|
|
1727
1751
|
try {
|
|
1728
1752
|
console.log(`[EdgeWorker] Fetching comments for issue ${issue.identifier}`);
|
|
1729
|
-
const comments = await
|
|
1730
|
-
filter: { issue: { id: { eq: issue.id } } },
|
|
1731
|
-
});
|
|
1753
|
+
const comments = await issueTracker.fetchComments(issue.id);
|
|
1732
1754
|
const commentNodes = comments.nodes;
|
|
1733
1755
|
if (commentNodes.length > 0) {
|
|
1734
1756
|
commentThreads = await this.formatCommentThreads(commentNodes);
|
|
@@ -1771,11 +1793,9 @@ IMPORTANT: Focus specifically on addressing the new comment above. This is a new
|
|
|
1771
1793
|
// Now replace the new comment variables
|
|
1772
1794
|
// We'll need to fetch the comment author
|
|
1773
1795
|
let authorName = "Unknown";
|
|
1774
|
-
if (
|
|
1796
|
+
if (issueTracker) {
|
|
1775
1797
|
try {
|
|
1776
|
-
const fullComment = await
|
|
1777
|
-
id: newComment.id,
|
|
1778
|
-
});
|
|
1798
|
+
const fullComment = await issueTracker.fetchComment(newComment.id);
|
|
1779
1799
|
const user = await fullComment.user;
|
|
1780
1800
|
authorName =
|
|
1781
1801
|
user?.displayName || user?.name || user?.email || "Unknown";
|
|
@@ -1876,13 +1896,13 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
|
|
|
1876
1896
|
/**
|
|
1877
1897
|
* Move issue to started state when assigned
|
|
1878
1898
|
* @param issue Full Linear issue object from Linear SDK
|
|
1879
|
-
* @param repositoryId Repository ID for
|
|
1899
|
+
* @param repositoryId Repository ID for issue tracker lookup
|
|
1880
1900
|
*/
|
|
1881
1901
|
async moveIssueToStartedState(issue, repositoryId) {
|
|
1882
1902
|
try {
|
|
1883
|
-
const
|
|
1884
|
-
if (!
|
|
1885
|
-
console.warn(`No
|
|
1903
|
+
const issueTracker = this.issueTrackers.get(repositoryId);
|
|
1904
|
+
if (!issueTracker) {
|
|
1905
|
+
console.warn(`No issue tracker found for repository ${repositoryId}, skipping state update`);
|
|
1886
1906
|
return;
|
|
1887
1907
|
}
|
|
1888
1908
|
// Check if issue is already in a started state
|
|
@@ -1898,9 +1918,7 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
|
|
|
1898
1918
|
return;
|
|
1899
1919
|
}
|
|
1900
1920
|
// Get available workflow states for the issue's team
|
|
1901
|
-
const teamStates = await
|
|
1902
|
-
filter: { team: { id: { eq: team.id } } },
|
|
1903
|
-
});
|
|
1921
|
+
const teamStates = await issueTracker.fetchWorkflowStates(team.id);
|
|
1904
1922
|
const states = teamStates;
|
|
1905
1923
|
// Find all states with type "started" and pick the one with lowest position
|
|
1906
1924
|
// This ensures we pick "In Progress" over "In Review" when both have type "started"
|
|
@@ -1916,7 +1934,7 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
|
|
|
1916
1934
|
console.warn(`Issue ${issue.identifier} has no ID, skipping state update`);
|
|
1917
1935
|
return;
|
|
1918
1936
|
}
|
|
1919
|
-
await
|
|
1937
|
+
await issueTracker.updateIssue(issue.id, {
|
|
1920
1938
|
stateId: startedState.id,
|
|
1921
1939
|
});
|
|
1922
1940
|
console.log(`✅ Successfully moved issue ${issue.identifier} to ${startedState.name} state`);
|
|
@@ -1931,35 +1949,33 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
|
|
|
1931
1949
|
*/
|
|
1932
1950
|
// private async postInitialComment(issueId: string, repositoryId: string): Promise<void> {
|
|
1933
1951
|
// const body = "I'm getting started right away."
|
|
1934
|
-
// // Get the
|
|
1935
|
-
// const
|
|
1936
|
-
// if (!
|
|
1937
|
-
// throw new Error(`No
|
|
1952
|
+
// // Get the issue tracker for this repository
|
|
1953
|
+
// const issueTracker = this.issueTrackers.get(repositoryId)
|
|
1954
|
+
// if (!issueTracker) {
|
|
1955
|
+
// throw new Error(`No issue tracker found for repository ${repositoryId}`)
|
|
1938
1956
|
// }
|
|
1939
1957
|
// const commentData = {
|
|
1940
|
-
// issueId,
|
|
1941
1958
|
// body
|
|
1942
1959
|
// }
|
|
1943
|
-
// await
|
|
1960
|
+
// await issueTracker.createComment(commentData)
|
|
1944
1961
|
// }
|
|
1945
1962
|
/**
|
|
1946
1963
|
* Post a comment to Linear
|
|
1947
1964
|
*/
|
|
1948
1965
|
async postComment(issueId, body, repositoryId, parentId) {
|
|
1949
|
-
// Get the
|
|
1950
|
-
const
|
|
1951
|
-
if (!
|
|
1952
|
-
throw new Error(`No
|
|
1966
|
+
// Get the issue tracker for this repository
|
|
1967
|
+
const issueTracker = this.issueTrackers.get(repositoryId);
|
|
1968
|
+
if (!issueTracker) {
|
|
1969
|
+
throw new Error(`No issue tracker found for repository ${repositoryId}`);
|
|
1953
1970
|
}
|
|
1954
|
-
const
|
|
1955
|
-
issueId,
|
|
1971
|
+
const commentInput = {
|
|
1956
1972
|
body,
|
|
1957
1973
|
};
|
|
1958
1974
|
// Add parent ID if provided (for reply)
|
|
1959
1975
|
if (parentId) {
|
|
1960
|
-
|
|
1976
|
+
commentInput.parentId = parentId;
|
|
1961
1977
|
}
|
|
1962
|
-
await
|
|
1978
|
+
await issueTracker.createComment(issueId, commentInput);
|
|
1963
1979
|
}
|
|
1964
1980
|
/**
|
|
1965
1981
|
* Format todos as Linear checklist markdown
|
|
@@ -2008,10 +2024,10 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
|
|
|
2008
2024
|
const descriptionUrls = this.extractAttachmentUrls(issue.description || "");
|
|
2009
2025
|
// Extract URLs from comments if available
|
|
2010
2026
|
const commentUrls = [];
|
|
2011
|
-
const
|
|
2027
|
+
const issueTracker = this.issueTrackers.get(repository.id);
|
|
2012
2028
|
// Fetch native Linear attachments (e.g., Sentry links)
|
|
2013
2029
|
const nativeAttachments = [];
|
|
2014
|
-
if (
|
|
2030
|
+
if (issueTracker && issue.id) {
|
|
2015
2031
|
try {
|
|
2016
2032
|
// Fetch native attachments using Linear SDK
|
|
2017
2033
|
console.log(`[EdgeWorker] Fetching native attachments for issue ${issue.identifier}`);
|
|
@@ -2030,9 +2046,7 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
|
|
|
2030
2046
|
console.error("Failed to fetch native attachments:", error);
|
|
2031
2047
|
}
|
|
2032
2048
|
try {
|
|
2033
|
-
const comments = await
|
|
2034
|
-
filter: { issue: { id: { eq: issue.id } } },
|
|
2035
|
-
});
|
|
2049
|
+
const comments = await issueTracker.fetchComments(issue.id);
|
|
2036
2050
|
const commentNodes = comments.nodes;
|
|
2037
2051
|
for (const comment of commentNodes) {
|
|
2038
2052
|
const urls = this.extractAttachmentUrls(comment.body);
|
|
@@ -2395,13 +2409,13 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
|
|
|
2395
2409
|
}
|
|
2396
2410
|
}
|
|
2397
2411
|
// Post thought to Linear showing feedback receipt
|
|
2398
|
-
const
|
|
2399
|
-
if (
|
|
2412
|
+
const issueTracker = this.issueTrackers.get(childRepo.id);
|
|
2413
|
+
if (issueTracker) {
|
|
2400
2414
|
const feedbackThought = parentIssueId
|
|
2401
2415
|
? `Received feedback from orchestrator (${parentIssueId}):\n\n---\n\n${message}\n\n---`
|
|
2402
2416
|
: `Received feedback from orchestrator:\n\n---\n\n${message}\n\n---`;
|
|
2403
2417
|
try {
|
|
2404
|
-
const result = await
|
|
2418
|
+
const result = await issueTracker.createAgentActivity({
|
|
2405
2419
|
agentSessionId: childSessionId,
|
|
2406
2420
|
content: {
|
|
2407
2421
|
type: "thought",
|
|
@@ -3024,9 +3038,9 @@ ${input.userComment}
|
|
|
3024
3038
|
*/
|
|
3025
3039
|
async postInstantAcknowledgment(linearAgentActivitySessionId, repositoryId) {
|
|
3026
3040
|
try {
|
|
3027
|
-
const
|
|
3028
|
-
if (!
|
|
3029
|
-
console.warn(`[EdgeWorker] No
|
|
3041
|
+
const issueTracker = this.issueTrackers.get(repositoryId);
|
|
3042
|
+
if (!issueTracker) {
|
|
3043
|
+
console.warn(`[EdgeWorker] No issue tracker found for repository ${repositoryId}`);
|
|
3030
3044
|
return;
|
|
3031
3045
|
}
|
|
3032
3046
|
const activityInput = {
|
|
@@ -3036,7 +3050,7 @@ ${input.userComment}
|
|
|
3036
3050
|
body: "I've received your request and I'm starting to work on it. Let me analyze the issue and prepare my approach.",
|
|
3037
3051
|
},
|
|
3038
3052
|
};
|
|
3039
|
-
const result = await
|
|
3053
|
+
const result = await issueTracker.createAgentActivity(activityInput);
|
|
3040
3054
|
if (result.success) {
|
|
3041
3055
|
console.log(`[EdgeWorker] Posted instant acknowledgment thought for session ${linearAgentActivitySessionId}`);
|
|
3042
3056
|
}
|
|
@@ -3053,9 +3067,9 @@ ${input.userComment}
|
|
|
3053
3067
|
*/
|
|
3054
3068
|
async postParentResumeAcknowledgment(linearAgentActivitySessionId, repositoryId) {
|
|
3055
3069
|
try {
|
|
3056
|
-
const
|
|
3057
|
-
if (!
|
|
3058
|
-
console.warn(`[EdgeWorker] No
|
|
3070
|
+
const issueTracker = this.issueTrackers.get(repositoryId);
|
|
3071
|
+
if (!issueTracker) {
|
|
3072
|
+
console.warn(`[EdgeWorker] No issue tracker found for repository ${repositoryId}`);
|
|
3059
3073
|
return;
|
|
3060
3074
|
}
|
|
3061
3075
|
const activityInput = {
|
|
@@ -3065,7 +3079,7 @@ ${input.userComment}
|
|
|
3065
3079
|
body: "Resuming from child session",
|
|
3066
3080
|
},
|
|
3067
3081
|
};
|
|
3068
|
-
const result = await
|
|
3082
|
+
const result = await issueTracker.createAgentActivity(activityInput);
|
|
3069
3083
|
if (result.success) {
|
|
3070
3084
|
console.log(`[EdgeWorker] Posted parent resumption acknowledgment thought for session ${linearAgentActivitySessionId}`);
|
|
3071
3085
|
}
|
|
@@ -3083,9 +3097,9 @@ ${input.userComment}
|
|
|
3083
3097
|
*/
|
|
3084
3098
|
async postRepositorySelectionActivity(linearAgentActivitySessionId, repositoryId, repositoryName, selectionMethod) {
|
|
3085
3099
|
try {
|
|
3086
|
-
const
|
|
3087
|
-
if (!
|
|
3088
|
-
console.warn(`[EdgeWorker] No
|
|
3100
|
+
const issueTracker = this.issueTrackers.get(repositoryId);
|
|
3101
|
+
if (!issueTracker) {
|
|
3102
|
+
console.warn(`[EdgeWorker] No issue tracker found for repository ${repositoryId}`);
|
|
3089
3103
|
return;
|
|
3090
3104
|
}
|
|
3091
3105
|
let methodDisplay;
|
|
@@ -3117,7 +3131,7 @@ ${input.userComment}
|
|
|
3117
3131
|
body: `Repository "${repositoryName}" has been ${methodDisplay}.`,
|
|
3118
3132
|
},
|
|
3119
3133
|
};
|
|
3120
|
-
const result = await
|
|
3134
|
+
const result = await issueTracker.createAgentActivity(activityInput);
|
|
3121
3135
|
if (result.success) {
|
|
3122
3136
|
console.log(`[EdgeWorker] Posted repository selection activity for session ${linearAgentActivitySessionId} (${selectionMethod})`);
|
|
3123
3137
|
}
|
|
@@ -3141,11 +3155,11 @@ ${input.userComment}
|
|
|
3141
3155
|
// Post ephemeral "Routing..." thought
|
|
3142
3156
|
await agentSessionManager.postRoutingThought(linearAgentActivitySessionId);
|
|
3143
3157
|
// Fetch full issue and labels to check for Orchestrator label override
|
|
3144
|
-
const
|
|
3158
|
+
const issueTracker = this.issueTrackers.get(repository.id);
|
|
3145
3159
|
let hasOrchestratorLabel = false;
|
|
3146
|
-
if (
|
|
3160
|
+
if (issueTracker) {
|
|
3147
3161
|
try {
|
|
3148
|
-
const fullIssue = await
|
|
3162
|
+
const fullIssue = await issueTracker.fetchIssue(session.issueId);
|
|
3149
3163
|
const labels = await this.fetchIssueLabels(fullIssue);
|
|
3150
3164
|
// Check for Orchestrator label (same logic as initial routing)
|
|
3151
3165
|
const orchestratorConfig = repository.labelPrompts?.orchestrator;
|
|
@@ -3241,9 +3255,9 @@ ${input.userComment}
|
|
|
3241
3255
|
*/
|
|
3242
3256
|
async postSystemPromptSelectionThought(linearAgentActivitySessionId, labels, repositoryId) {
|
|
3243
3257
|
try {
|
|
3244
|
-
const
|
|
3245
|
-
if (!
|
|
3246
|
-
console.warn(`[EdgeWorker] No
|
|
3258
|
+
const issueTracker = this.issueTrackers.get(repositoryId);
|
|
3259
|
+
if (!issueTracker) {
|
|
3260
|
+
console.warn(`[EdgeWorker] No issue tracker found for repository ${repositoryId}`);
|
|
3247
3261
|
return;
|
|
3248
3262
|
}
|
|
3249
3263
|
// Determine which prompt type was selected and which label triggered it
|
|
@@ -3309,7 +3323,7 @@ ${input.userComment}
|
|
|
3309
3323
|
body: `Entering '${selectedPromptType}' mode because of the '${triggerLabel}' label. I'll follow the ${selectedPromptType} process...`,
|
|
3310
3324
|
},
|
|
3311
3325
|
};
|
|
3312
|
-
const result = await
|
|
3326
|
+
const result = await issueTracker.createAgentActivity(activityInput);
|
|
3313
3327
|
if (result.success) {
|
|
3314
3328
|
console.log(`[EdgeWorker] Posted system prompt selection thought for session ${linearAgentActivitySessionId} (${selectedPromptType} mode)`);
|
|
3315
3329
|
}
|
|
@@ -3401,9 +3415,9 @@ ${input.userComment}
|
|
|
3401
3415
|
*/
|
|
3402
3416
|
async postInstantPromptedAcknowledgment(linearAgentActivitySessionId, repositoryId, isStreaming) {
|
|
3403
3417
|
try {
|
|
3404
|
-
const
|
|
3405
|
-
if (!
|
|
3406
|
-
console.warn(`[EdgeWorker] No
|
|
3418
|
+
const issueTracker = this.issueTrackers.get(repositoryId);
|
|
3419
|
+
if (!issueTracker) {
|
|
3420
|
+
console.warn(`[EdgeWorker] No issue tracker found for repository ${repositoryId}`);
|
|
3407
3421
|
return;
|
|
3408
3422
|
}
|
|
3409
3423
|
const message = isStreaming
|
|
@@ -3416,7 +3430,7 @@ ${input.userComment}
|
|
|
3416
3430
|
body: message,
|
|
3417
3431
|
},
|
|
3418
3432
|
};
|
|
3419
|
-
const result = await
|
|
3433
|
+
const result = await issueTracker.createAgentActivity(activityInput);
|
|
3420
3434
|
if (result.success) {
|
|
3421
3435
|
console.log(`[EdgeWorker] Posted instant prompted acknowledgment thought for session ${linearAgentActivitySessionId} (streaming: ${isStreaming})`);
|
|
3422
3436
|
}
|
|
@@ -3432,14 +3446,14 @@ ${input.userComment}
|
|
|
3432
3446
|
* Fetch complete issue details from Linear API
|
|
3433
3447
|
*/
|
|
3434
3448
|
async fetchFullIssueDetails(issueId, repositoryId) {
|
|
3435
|
-
const
|
|
3436
|
-
if (!
|
|
3437
|
-
console.warn(`[EdgeWorker] No
|
|
3449
|
+
const issueTracker = this.issueTrackers.get(repositoryId);
|
|
3450
|
+
if (!issueTracker) {
|
|
3451
|
+
console.warn(`[EdgeWorker] No issue tracker found for repository ${repositoryId}`);
|
|
3438
3452
|
return null;
|
|
3439
3453
|
}
|
|
3440
3454
|
try {
|
|
3441
3455
|
console.log(`[EdgeWorker] Fetching full issue details for ${issueId}`);
|
|
3442
|
-
const fullIssue = await
|
|
3456
|
+
const fullIssue = await issueTracker.fetchIssue(issueId);
|
|
3443
3457
|
console.log(`[EdgeWorker] Successfully fetched issue details for ${issueId}`);
|
|
3444
3458
|
// Check if issue has a parent
|
|
3445
3459
|
try {
|