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.
- package/dist/AgentSessionManager.d.ts +35 -37
- package/dist/AgentSessionManager.d.ts.map +1 -1
- package/dist/AgentSessionManager.js +102 -402
- package/dist/AgentSessionManager.js.map +1 -1
- package/dist/EdgeWorker.d.ts +25 -7
- package/dist/EdgeWorker.d.ts.map +1 -1
- package/dist/EdgeWorker.js +280 -149
- package/dist/EdgeWorker.js.map +1 -1
- package/dist/procedures/ProcedureRouter.d.ts +5 -3
- package/dist/procedures/ProcedureRouter.d.ts.map +1 -1
- package/dist/procedures/ProcedureRouter.js +25 -11
- package/dist/procedures/ProcedureRouter.js.map +1 -1
- package/dist/procedures/registry.d.ts +5 -5
- package/dist/procedures/registry.js +5 -5
- package/dist/procedures/registry.js.map +1 -1
- package/dist/procedures/types.d.ts +3 -2
- package/dist/procedures/types.d.ts.map +1 -1
- package/package.json +8 -7
|
@@ -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
|
-
|
|
21
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
81
|
+
* Create a session entry from user/assistant message (without syncing to Linear)
|
|
72
82
|
*/
|
|
73
|
-
async createSessionEntry(
|
|
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
|
-
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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,
|
|
552
|
-
//
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
804
|
-
if (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
|
|
808
|
-
const formattedParameter =
|
|
809
|
-
const formattedResult =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
682
|
+
* Add or update agent runner for a session
|
|
983
683
|
*/
|
|
984
|
-
|
|
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.
|
|
690
|
+
session.agentRunner = agentRunner;
|
|
991
691
|
session.updatedAt = Date.now();
|
|
992
|
-
console.log(`[AgentSessionManager] Added
|
|
692
|
+
console.log(`[AgentSessionManager] Added agent runner to session ${linearAgentActivitySessionId}`);
|
|
993
693
|
}
|
|
994
694
|
/**
|
|
995
|
-
* Get all
|
|
695
|
+
* Get all agent runners
|
|
996
696
|
*/
|
|
997
|
-
|
|
697
|
+
getAllAgentRunners() {
|
|
998
698
|
return Array.from(this.sessions.values())
|
|
999
|
-
.map((session) => session.
|
|
699
|
+
.map((session) => session.agentRunner)
|
|
1000
700
|
.filter((runner) => runner !== undefined);
|
|
1001
701
|
}
|
|
1002
702
|
/**
|
|
1003
|
-
* Get all
|
|
703
|
+
* Get all agent runners for a specific issue
|
|
1004
704
|
*/
|
|
1005
|
-
|
|
705
|
+
getAgentRunnersForIssue(issueId) {
|
|
1006
706
|
return Array.from(this.sessions.values())
|
|
1007
707
|
.filter((session) => session.issueId === issueId)
|
|
1008
|
-
.map((session) => session.
|
|
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
|
|
731
|
+
* Get agent runner for a specific session
|
|
1032
732
|
*/
|
|
1033
|
-
|
|
733
|
+
getAgentRunner(linearAgentActivitySessionId) {
|
|
1034
734
|
const session = this.sessions.get(linearAgentActivitySessionId);
|
|
1035
|
-
return session?.
|
|
735
|
+
return session?.agentRunner;
|
|
1036
736
|
}
|
|
1037
737
|
/**
|
|
1038
|
-
* Check if
|
|
738
|
+
* Check if an agent runner exists for a session
|
|
1039
739
|
*/
|
|
1040
|
-
|
|
740
|
+
hasAgentRunner(linearAgentActivitySessionId) {
|
|
1041
741
|
const session = this.sessions.get(linearAgentActivitySessionId);
|
|
1042
|
-
return session?.
|
|
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
|
|
1244
|
-
const {
|
|
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
|