cyrus-edge-worker 0.2.3 → 0.2.4

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.
@@ -1,3 +1,4 @@
1
+ import { EventEmitter } from "node:events";
1
2
  import { AgentActivitySignal, AgentSessionStatus, AgentSessionType, } from "cyrus-core";
2
3
  /**
3
4
  * Manages Agent Sessions integration with Claude Code SDK
@@ -6,7 +7,7 @@ import { AgentActivitySignal, AgentSessionStatus, AgentSessionType, } from "cyru
6
7
  *
7
8
  * CURRENTLY BEING HANDLED 'per repository'
8
9
  */
9
- export class AgentSessionManager {
10
+ export class AgentSessionManager extends EventEmitter {
10
11
  issueTracker;
11
12
  sessions = new Map();
12
13
  entries = new Map(); // Stores a list of session entries per each session by its linearAgentActivitySessionId
@@ -17,12 +18,11 @@ export class AgentSessionManager {
17
18
  sharedApplicationServer;
18
19
  getParentSessionId;
19
20
  resumeParentSession;
20
- resumeNextSubroutine;
21
- constructor(issueTracker, getParentSessionId, resumeParentSession, resumeNextSubroutine, procedureRouter, sharedApplicationServer) {
21
+ constructor(issueTracker, getParentSessionId, resumeParentSession, procedureRouter, sharedApplicationServer) {
22
+ super();
22
23
  this.issueTracker = issueTracker;
23
24
  this.getParentSessionId = getParentSessionId;
24
25
  this.resumeParentSession = resumeParentSession;
25
- this.resumeNextSubroutine = resumeNextSubroutine;
26
26
  this.procedureRouter = procedureRouter;
27
27
  this.sharedApplicationServer = sharedApplicationServer;
28
28
  }
@@ -49,7 +49,8 @@ export class AgentSessionManager {
49
49
  return agentSession;
50
50
  }
51
51
  /**
52
- * Create a new Agent Session from Claude system initialization
52
+ * Update Agent Session with session ID from system initialization
53
+ * Automatically detects whether it's Claude or Gemini based on the runner
53
54
  */
54
55
  updateAgentSessionWithClaudeSessionId(linearAgentActivitySessionId, claudeSystemMessage) {
55
56
  const linearSession = this.sessions.get(linearAgentActivitySessionId);
@@ -57,7 +58,16 @@ export class AgentSessionManager {
57
58
  console.warn(`[AgentSessionManager] No Linear session found for linearAgentActivitySessionId ${linearAgentActivitySessionId}`);
58
59
  return;
59
60
  }
60
- linearSession.claudeSessionId = claudeSystemMessage.session_id;
61
+ // Determine which runner is being used
62
+ const runner = linearSession.agentRunner;
63
+ const isGeminiRunner = runner?.constructor.name === "GeminiRunner";
64
+ // Update the appropriate session ID based on runner type
65
+ if (isGeminiRunner) {
66
+ linearSession.geminiSessionId = claudeSystemMessage.session_id;
67
+ }
68
+ else {
69
+ linearSession.claudeSessionId = claudeSystemMessage.session_id;
70
+ }
61
71
  linearSession.updatedAt = Date.now();
62
72
  linearSession.metadata = {
63
73
  ...linearSession.metadata, // Preserve existing metadata
@@ -68,17 +78,24 @@ export class AgentSessionManager {
68
78
  };
69
79
  }
70
80
  /**
71
- * Create a session entry from Claude user/assistant message (without syncing to Linear)
81
+ * Create a session entry from user/assistant message (without syncing to Linear)
72
82
  */
73
- async createSessionEntry(_linearAgentActivitySessionId, sdkMessage) {
83
+ async createSessionEntry(linearAgentActivitySessionId, sdkMessage) {
74
84
  // Extract tool info if this is an assistant message
75
85
  const toolInfo = sdkMessage.type === "assistant" ? this.extractToolInfo(sdkMessage) : null;
76
86
  // Extract tool_use_id and error status if this is a user message with tool_result
77
87
  const toolResultInfo = sdkMessage.type === "user"
78
88
  ? this.extractToolResultInfo(sdkMessage)
79
89
  : null;
90
+ // Determine which runner is being used
91
+ const session = this.sessions.get(linearAgentActivitySessionId);
92
+ const runner = session?.agentRunner;
93
+ const isGeminiRunner = runner?.constructor.name === "GeminiRunner";
80
94
  const sessionEntry = {
81
- claudeSessionId: sdkMessage.session_id,
95
+ // Set the appropriate session ID based on runner type
96
+ ...(isGeminiRunner
97
+ ? { geminiSessionId: sdkMessage.session_id }
98
+ : { claudeSessionId: sdkMessage.session_id }),
82
99
  type: sdkMessage.type,
83
100
  content: this.extractContent(sdkMessage),
84
101
  metadata: {
@@ -98,354 +115,6 @@ export class AgentSessionManager {
98
115
  // DON'T store locally yet - wait until we actually post to Linear
99
116
  return sessionEntry;
100
117
  }
101
- /**
102
- * Format TodoWrite tool parameter as a nice checklist
103
- */
104
- formatTodoWriteParameter(jsonContent) {
105
- try {
106
- const data = JSON.parse(jsonContent);
107
- if (!data.todos || !Array.isArray(data.todos)) {
108
- return jsonContent;
109
- }
110
- const todos = data.todos;
111
- // Keep original order but add status indicators
112
- let formatted = "\n";
113
- todos.forEach((todo, index) => {
114
- let statusEmoji = "";
115
- if (todo.status === "completed") {
116
- statusEmoji = "✅ ";
117
- }
118
- else if (todo.status === "in_progress") {
119
- statusEmoji = "🔄 ";
120
- }
121
- else if (todo.status === "pending") {
122
- statusEmoji = "⏳ ";
123
- }
124
- formatted += `${statusEmoji}${todo.content}`;
125
- if (index < todos.length - 1) {
126
- formatted += "\n";
127
- }
128
- });
129
- return formatted;
130
- }
131
- catch (error) {
132
- console.error("[AgentSessionManager] Failed to format TodoWrite parameter:", error);
133
- return jsonContent;
134
- }
135
- }
136
- /**
137
- * Format tool input for display in Linear agent activities
138
- * Converts raw tool inputs into user-friendly parameter strings
139
- */
140
- formatToolParameter(toolName, toolInput) {
141
- // If input is already a string, return it
142
- if (typeof toolInput === "string") {
143
- return toolInput;
144
- }
145
- try {
146
- switch (toolName) {
147
- case "Bash":
148
- case "↪ Bash": {
149
- // Show command only - description goes in action field via formatToolActionName
150
- return toolInput.command || JSON.stringify(toolInput);
151
- }
152
- case "Read":
153
- case "↪ Read":
154
- if (toolInput.file_path) {
155
- let param = toolInput.file_path;
156
- if (toolInput.offset !== undefined ||
157
- toolInput.limit !== undefined) {
158
- const start = toolInput.offset || 0;
159
- const end = toolInput.limit ? start + toolInput.limit : "end";
160
- param += ` (lines ${start + 1}-${end})`;
161
- }
162
- return param;
163
- }
164
- break;
165
- case "Edit":
166
- case "↪ Edit":
167
- if (toolInput.file_path) {
168
- return toolInput.file_path;
169
- }
170
- break;
171
- case "Write":
172
- case "↪ Write":
173
- if (toolInput.file_path) {
174
- return toolInput.file_path;
175
- }
176
- break;
177
- case "Grep":
178
- case "↪ Grep":
179
- if (toolInput.pattern) {
180
- let param = `Pattern: \`${toolInput.pattern}\``;
181
- if (toolInput.path) {
182
- param += ` in ${toolInput.path}`;
183
- }
184
- if (toolInput.glob) {
185
- param += ` (${toolInput.glob})`;
186
- }
187
- if (toolInput.type) {
188
- param += ` [${toolInput.type} files]`;
189
- }
190
- return param;
191
- }
192
- break;
193
- case "Glob":
194
- case "↪ Glob":
195
- if (toolInput.pattern) {
196
- let param = `Pattern: \`${toolInput.pattern}\``;
197
- if (toolInput.path) {
198
- param += ` in ${toolInput.path}`;
199
- }
200
- return param;
201
- }
202
- break;
203
- case "Task":
204
- case "↪ Task":
205
- if (toolInput.description) {
206
- return toolInput.description;
207
- }
208
- break;
209
- case "WebFetch":
210
- case "↪ WebFetch":
211
- if (toolInput.url) {
212
- return toolInput.url;
213
- }
214
- break;
215
- case "WebSearch":
216
- case "↪ WebSearch":
217
- if (toolInput.query) {
218
- return `Query: ${toolInput.query}`;
219
- }
220
- break;
221
- case "NotebookEdit":
222
- case "↪ NotebookEdit":
223
- if (toolInput.notebook_path) {
224
- let param = toolInput.notebook_path;
225
- if (toolInput.cell_id) {
226
- param += ` (cell ${toolInput.cell_id})`;
227
- }
228
- return param;
229
- }
230
- break;
231
- default:
232
- // For MCP tools or other unknown tools, try to extract meaningful info
233
- if (toolName.startsWith("mcp__")) {
234
- // Extract key fields that are commonly meaningful
235
- const meaningfulFields = [
236
- "query",
237
- "id",
238
- "issueId",
239
- "title",
240
- "name",
241
- "path",
242
- "file",
243
- ];
244
- for (const field of meaningfulFields) {
245
- if (toolInput[field]) {
246
- return `${field}: ${toolInput[field]}`;
247
- }
248
- }
249
- }
250
- break;
251
- }
252
- // Fallback to JSON but make it compact
253
- return JSON.stringify(toolInput);
254
- }
255
- catch (error) {
256
- console.error("[AgentSessionManager] Failed to format tool parameter:", error);
257
- return JSON.stringify(toolInput);
258
- }
259
- }
260
- /**
261
- * Format tool action name with description for Bash tool
262
- * Puts the description in round brackets after the tool name in the action field
263
- */
264
- formatToolActionName(toolName, toolInput, isError) {
265
- // Handle Bash tool with description
266
- if (toolName === "Bash" || toolName === "↪ Bash") {
267
- // Check if toolInput has a description field
268
- if (toolInput &&
269
- typeof toolInput === "object" &&
270
- "description" in toolInput &&
271
- toolInput.description) {
272
- const baseName = isError ? `${toolName} (Error)` : toolName;
273
- return `${baseName} (${toolInput.description})`;
274
- }
275
- }
276
- // Default formatting for other tools or Bash without description
277
- return isError ? `${toolName} (Error)` : toolName;
278
- }
279
- /**
280
- * Format tool result for display in Linear agent activities
281
- * Converts raw tool results into formatted Markdown
282
- */
283
- formatToolResult(toolName, toolInput, result, isError) {
284
- // If there's an error, wrap in error formatting
285
- if (isError) {
286
- return `\`\`\`\n${result}\n\`\`\``;
287
- }
288
- try {
289
- switch (toolName) {
290
- case "Bash":
291
- case "↪ Bash": {
292
- // Show command first if not already in parameter
293
- let formatted = "";
294
- if (toolInput.command && !toolInput.description) {
295
- formatted += `\`\`\`bash\n${toolInput.command}\n\`\`\`\n\n`;
296
- }
297
- // Then show output
298
- if (result?.trim()) {
299
- formatted += `\`\`\`\n${result}\n\`\`\``;
300
- }
301
- else {
302
- formatted += "*No output*";
303
- }
304
- return formatted;
305
- }
306
- case "Read":
307
- case "↪ Read":
308
- // For Read, the result is file content - use code block
309
- if (result?.trim()) {
310
- // Clean up the result: remove line numbers and system-reminder tags
311
- let cleanedResult = result;
312
- // Remove line numbers (format: " 123→")
313
- cleanedResult = cleanedResult.replace(/^\s*\d+→/gm, "");
314
- // Remove system-reminder blocks
315
- cleanedResult = cleanedResult.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "");
316
- // Trim only blank lines (not horizontal whitespace) to preserve indentation
317
- cleanedResult = cleanedResult
318
- .replace(/^\n+/, "")
319
- .replace(/\n+$/, "");
320
- // Try to detect language from file extension
321
- let lang = "";
322
- if (toolInput.file_path) {
323
- const ext = toolInput.file_path.split(".").pop()?.toLowerCase();
324
- const langMap = {
325
- ts: "typescript",
326
- tsx: "typescript",
327
- js: "javascript",
328
- jsx: "javascript",
329
- py: "python",
330
- rb: "ruby",
331
- go: "go",
332
- rs: "rust",
333
- java: "java",
334
- c: "c",
335
- cpp: "cpp",
336
- cs: "csharp",
337
- php: "php",
338
- swift: "swift",
339
- kt: "kotlin",
340
- scala: "scala",
341
- sh: "bash",
342
- bash: "bash",
343
- zsh: "bash",
344
- yml: "yaml",
345
- yaml: "yaml",
346
- json: "json",
347
- xml: "xml",
348
- html: "html",
349
- css: "css",
350
- scss: "scss",
351
- md: "markdown",
352
- sql: "sql",
353
- };
354
- lang = langMap[ext || ""] || "";
355
- }
356
- return `\`\`\`${lang}\n${cleanedResult}\n\`\`\``;
357
- }
358
- return "*Empty file*";
359
- case "Edit":
360
- case "↪ Edit": {
361
- // For Edit, show changes as a diff
362
- // Extract old_string and new_string from toolInput
363
- if (toolInput.old_string && toolInput.new_string) {
364
- // Format as a unified diff
365
- const oldLines = toolInput.old_string.split("\n");
366
- const newLines = toolInput.new_string.split("\n");
367
- let diff = "```diff\n";
368
- // Add context lines before changes (show all old lines with - prefix)
369
- for (const line of oldLines) {
370
- diff += `-${line}\n`;
371
- }
372
- // Add new lines with + prefix
373
- for (const line of newLines) {
374
- diff += `+${line}\n`;
375
- }
376
- diff += "```";
377
- return diff;
378
- }
379
- // Fallback to result if old/new strings not available
380
- if (result?.trim()) {
381
- return result;
382
- }
383
- return "*Edit completed*";
384
- }
385
- case "Write":
386
- case "↪ Write":
387
- // For Write, just confirm
388
- if (result?.trim()) {
389
- return result; // In case there's an error or message
390
- }
391
- return "*File written successfully*";
392
- case "Grep":
393
- case "↪ Grep": {
394
- // Format grep results
395
- if (result?.trim()) {
396
- const lines = result.split("\n");
397
- // If it looks like file paths (files_with_matches mode)
398
- if (lines.length > 0 &&
399
- lines[0] &&
400
- !lines[0].includes(":") &&
401
- lines[0].trim().length > 0) {
402
- return `Found ${lines.filter((l) => l.trim()).length} matching files:\n\`\`\`\n${result}\n\`\`\``;
403
- }
404
- // Otherwise it's content matches
405
- return `\`\`\`\n${result}\n\`\`\``;
406
- }
407
- return "*No matches found*";
408
- }
409
- case "Glob":
410
- case "↪ Glob": {
411
- if (result?.trim()) {
412
- const lines = result.split("\n").filter((l) => l.trim());
413
- return `Found ${lines.length} matching files:\n\`\`\`\n${result}\n\`\`\``;
414
- }
415
- return "*No files found*";
416
- }
417
- case "Task":
418
- case "↪ Task":
419
- // Task results can be complex - keep as is but in code block if multiline
420
- if (result?.trim()) {
421
- if (result.includes("\n")) {
422
- return `\`\`\`\n${result}\n\`\`\``;
423
- }
424
- return result;
425
- }
426
- return "*Task completed*";
427
- case "WebFetch":
428
- case "↪ WebFetch":
429
- case "WebSearch":
430
- case "↪ WebSearch":
431
- // Web results are usually formatted, keep as is
432
- return result || "*No results*";
433
- default:
434
- // For unknown tools, use code block if result has multiple lines
435
- if (result?.trim()) {
436
- if (result.includes("\n") && result.length > 100) {
437
- return `\`\`\`\n${result}\n\`\`\``;
438
- }
439
- return result;
440
- }
441
- return "*Completed*";
442
- }
443
- }
444
- catch (error) {
445
- console.error("[AgentSessionManager] Failed to format tool result:", error);
446
- return result || "";
447
- }
448
- }
449
118
  /**
450
119
  * Complete a session from Claude result message
451
120
  */
@@ -485,9 +154,10 @@ export class AgentSessionManager {
485
154
  console.log(`[AgentSessionManager] Subroutine completed with error, not triggering next subroutine`);
486
155
  return;
487
156
  }
488
- const claudeSessionId = session.claudeSessionId;
489
- if (!claudeSessionId) {
490
- console.error(`[AgentSessionManager] No Claude session ID found for procedure session`);
157
+ // Get the session ID (either Claude or Gemini)
158
+ const sessionId = session.claudeSessionId || session.geminiSessionId;
159
+ if (!sessionId) {
160
+ console.error(`[AgentSessionManager] No session ID found for procedure session`);
491
161
  return;
492
162
  }
493
163
  // Check if there's a next subroutine
@@ -548,16 +218,13 @@ export class AgentSessionManager {
548
218
  }
549
219
  // Advance procedure state
550
220
  console.log(`[AgentSessionManager] Subroutine completed, advancing to next: ${nextSubroutine.name}`);
551
- this.procedureRouter.advanceToNextSubroutine(session, claudeSessionId);
552
- // Trigger next subroutine
553
- if (this.resumeNextSubroutine) {
554
- try {
555
- await this.resumeNextSubroutine(linearAgentActivitySessionId);
556
- }
557
- catch (error) {
558
- console.error(`[AgentSessionManager] Failed to trigger next subroutine:`, error);
559
- }
560
- }
221
+ this.procedureRouter.advanceToNextSubroutine(session, sessionId);
222
+ // Emit event for EdgeWorker to handle subroutine transition
223
+ // This replaces the callback pattern and allows EdgeWorker to subscribe
224
+ this.emit("subroutineComplete", {
225
+ linearAgentActivitySessionId,
226
+ session,
227
+ });
561
228
  }
562
229
  else {
563
230
  // Procedure complete - post final result
@@ -653,11 +320,18 @@ export class AgentSessionManager {
653
320
  this.sessions.set(linearAgentActivitySessionId, session);
654
321
  }
655
322
  /**
656
- * Add result entry from Claude result message
323
+ * Add result entry from result message
657
324
  */
658
325
  async addResultEntry(linearAgentActivitySessionId, resultMessage) {
326
+ // Determine which runner is being used
327
+ const session = this.sessions.get(linearAgentActivitySessionId);
328
+ const runner = session?.agentRunner;
329
+ const isGeminiRunner = runner?.constructor.name === "GeminiRunner";
659
330
  const resultEntry = {
660
- claudeSessionId: resultMessage.session_id,
331
+ // Set the appropriate session ID based on runner type
332
+ ...(isGeminiRunner
333
+ ? { geminiSessionId: resultMessage.session_id }
334
+ : { claudeSessionId: resultMessage.session_id }),
661
335
  type: "result",
662
336
  content: "result" in resultMessage ? resultMessage.result : "",
663
337
  metadata: {
@@ -800,15 +474,23 @@ export class AgentSessionManager {
800
474
  if (entry.metadata.toolUseId) {
801
475
  this.toolCallsByToolUseId.delete(entry.metadata.toolUseId);
802
476
  }
803
- // Skip creating activity for TodoWrite results since TodoWrite already created a non-ephemeral thought
804
- if (toolName === "TodoWrite" || toolName === "↪ TodoWrite") {
477
+ // Skip creating activity for TodoWrite/write_todos results since they already created a non-ephemeral thought
478
+ if (toolName === "TodoWrite" ||
479
+ toolName === "↪ TodoWrite" ||
480
+ toolName === "write_todos") {
481
+ return;
482
+ }
483
+ // Get formatter from runner
484
+ const formatter = session.agentRunner?.getFormatter();
485
+ if (!formatter) {
486
+ console.warn(`[AgentSessionManager] No formatter available for session ${linearAgentActivitySessionId}`);
805
487
  return;
806
488
  }
807
- // Format parameter and result using our formatters
808
- const formattedParameter = this.formatToolParameter(toolName, toolInput);
809
- const formattedResult = this.formatToolResult(toolName, toolInput, toolResult.content?.trim() || "", toolResult.isError);
489
+ // Format parameter and result using runner's formatter
490
+ const formattedParameter = formatter.formatToolParameter(toolName, toolInput);
491
+ const formattedResult = formatter.formatToolResult(toolName, toolInput, toolResult.content?.trim() || "", toolResult.isError);
810
492
  // Format the action name (with description for Bash tool)
811
- const formattedAction = this.formatToolActionName(toolName, toolInput, toolResult.isError);
493
+ const formattedAction = formatter.formatToolActionName(toolName, toolInput, toolResult.isError);
812
494
  content = {
813
495
  type: "action",
814
496
  action: formattedAction,
@@ -844,20 +526,32 @@ export class AgentSessionManager {
844
526
  input: entry.metadata.toolInput || entry.content,
845
527
  });
846
528
  }
847
- // Special handling for TodoWrite tool - treat as thought instead of action
848
- if (toolName === "TodoWrite") {
849
- const formattedTodos = this.formatTodoWriteParameter(entry.content);
529
+ // Special handling for TodoWrite tool (Claude) and write_todos (Gemini) - treat as thought instead of action
530
+ if (toolName === "TodoWrite" || toolName === "write_todos") {
531
+ // Get formatter from runner
532
+ const formatter = session.agentRunner?.getFormatter();
533
+ if (!formatter) {
534
+ console.warn(`[AgentSessionManager] No formatter available for session ${linearAgentActivitySessionId}`);
535
+ return;
536
+ }
537
+ const formattedTodos = formatter.formatTodoWriteParameter(entry.content);
850
538
  content = {
851
539
  type: "thought",
852
540
  body: formattedTodos,
853
541
  };
854
- // TodoWrite is not ephemeral
542
+ // TodoWrite/write_todos is not ephemeral
855
543
  ephemeral = false;
856
544
  }
857
545
  else if (toolName === "Task") {
546
+ // Get formatter from runner
547
+ const formatter = session.agentRunner?.getFormatter();
548
+ if (!formatter) {
549
+ console.warn(`[AgentSessionManager] No formatter available for session ${linearAgentActivitySessionId}`);
550
+ return;
551
+ }
858
552
  // Special handling for Task tool - add start marker and track active task
859
553
  const toolInput = entry.metadata.toolInput || entry.content;
860
- const formattedParameter = this.formatToolParameter(toolName, toolInput);
554
+ const formattedParameter = formatter.formatToolParameter(toolName, toolInput);
861
555
  const displayName = toolName;
862
556
  // Track this as the active Task for this session
863
557
  if (entry.metadata?.toolUseId) {
@@ -873,6 +567,12 @@ export class AgentSessionManager {
873
567
  ephemeral = false;
874
568
  }
875
569
  else {
570
+ // Get formatter from runner
571
+ const formatter = session.agentRunner?.getFormatter();
572
+ if (!formatter) {
573
+ console.warn(`[AgentSessionManager] No formatter available for session ${linearAgentActivitySessionId}`);
574
+ return;
575
+ }
876
576
  // Other tools - check if they're within an active Task
877
577
  const toolInput = entry.metadata.toolInput || entry.content;
878
578
  let displayName = toolName;
@@ -882,7 +582,7 @@ export class AgentSessionManager {
882
582
  displayName = `↪ ${toolName}`;
883
583
  }
884
584
  }
885
- const formattedParameter = this.formatToolParameter(displayName, toolInput);
585
+ const formattedParameter = formatter.formatToolParameter(displayName, toolInput);
886
586
  content = {
887
587
  type: "action",
888
588
  action: displayName,
@@ -979,33 +679,33 @@ export class AgentSessionManager {
979
679
  return Array.from(this.sessions.values()).filter((session) => session.status === AgentSessionStatus.Active);
980
680
  }
981
681
  /**
982
- * Add or update ClaudeRunner for a session
682
+ * Add or update agent runner for a session
983
683
  */
984
- addClaudeRunner(linearAgentActivitySessionId, claudeRunner) {
684
+ addAgentRunner(linearAgentActivitySessionId, agentRunner) {
985
685
  const session = this.sessions.get(linearAgentActivitySessionId);
986
686
  if (!session) {
987
687
  console.warn(`[AgentSessionManager] No session found for linearAgentActivitySessionId ${linearAgentActivitySessionId}`);
988
688
  return;
989
689
  }
990
- session.claudeRunner = claudeRunner;
690
+ session.agentRunner = agentRunner;
991
691
  session.updatedAt = Date.now();
992
- console.log(`[AgentSessionManager] Added ClaudeRunner to session ${linearAgentActivitySessionId}`);
692
+ console.log(`[AgentSessionManager] Added agent runner to session ${linearAgentActivitySessionId}`);
993
693
  }
994
694
  /**
995
- * Get all ClaudeRunners
695
+ * Get all agent runners
996
696
  */
997
- getAllClaudeRunners() {
697
+ getAllAgentRunners() {
998
698
  return Array.from(this.sessions.values())
999
- .map((session) => session.claudeRunner)
699
+ .map((session) => session.agentRunner)
1000
700
  .filter((runner) => runner !== undefined);
1001
701
  }
1002
702
  /**
1003
- * Get all ClaudeRunners for a specific issue
703
+ * Get all agent runners for a specific issue
1004
704
  */
1005
- getClaudeRunnersForIssue(issueId) {
705
+ getAgentRunnersForIssue(issueId) {
1006
706
  return Array.from(this.sessions.values())
1007
707
  .filter((session) => session.issueId === issueId)
1008
- .map((session) => session.claudeRunner)
708
+ .map((session) => session.agentRunner)
1009
709
  .filter((runner) => runner !== undefined);
1010
710
  }
1011
711
  /**
@@ -1028,18 +728,18 @@ export class AgentSessionManager {
1028
728
  return Array.from(this.sessions.values());
1029
729
  }
1030
730
  /**
1031
- * Get ClaudeRunner for a specific session
731
+ * Get agent runner for a specific session
1032
732
  */
1033
- getClaudeRunner(linearAgentActivitySessionId) {
733
+ getAgentRunner(linearAgentActivitySessionId) {
1034
734
  const session = this.sessions.get(linearAgentActivitySessionId);
1035
- return session?.claudeRunner;
735
+ return session?.agentRunner;
1036
736
  }
1037
737
  /**
1038
- * Check if a ClaudeRunner exists for a session
738
+ * Check if an agent runner exists for a session
1039
739
  */
1040
- hasClaudeRunner(linearAgentActivitySessionId) {
740
+ hasAgentRunner(linearAgentActivitySessionId) {
1041
741
  const session = this.sessions.get(linearAgentActivitySessionId);
1042
- return session?.claudeRunner !== undefined;
742
+ return session?.agentRunner !== undefined;
1043
743
  }
1044
744
  /**
1045
745
  * Create a thought activity
@@ -1240,8 +940,8 @@ export class AgentSessionManager {
1240
940
  const entries = {};
1241
941
  // Serialize sessions
1242
942
  for (const [sessionId, session] of this.sessions.entries()) {
1243
- // Exclude claudeRunner from serialization as it's not serializable
1244
- const { claudeRunner: _claudeRunner, ...serializableSession } = session;
943
+ // Exclude agentRunner from serialization as it's not serializable
944
+ const { agentRunner: _agentRunner, ...serializableSession } = session;
1245
945
  sessions[sessionId] = serializableSession;
1246
946
  }
1247
947
  // Serialize entries