cyrus-edge-worker 0.2.2 → 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 +37 -40
- package/dist/AgentSessionManager.d.ts.map +1 -1
- package/dist/AgentSessionManager.js +127 -425
- package/dist/AgentSessionManager.js.map +1 -1
- package/dist/EdgeWorker.d.ts +29 -11
- package/dist/EdgeWorker.d.ts.map +1 -1
- package/dist/EdgeWorker.js +435 -290
- 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/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/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 +9 -8
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { AgentActivitySignal, AgentSessionStatus, AgentSessionType, } from "cyrus-core";
|
|
2
3
|
/**
|
|
3
|
-
* Manages
|
|
4
|
+
* Manages Agent Sessions integration with Claude Code SDK
|
|
4
5
|
* Transforms Claude streaming messages into Agent Session format
|
|
5
6
|
* Handles session lifecycle: create → active → complete/error
|
|
6
7
|
*
|
|
7
8
|
* CURRENTLY BEING HANDLED 'per repository'
|
|
8
9
|
*/
|
|
9
|
-
export class AgentSessionManager {
|
|
10
|
-
|
|
10
|
+
export class AgentSessionManager extends EventEmitter {
|
|
11
|
+
issueTracker;
|
|
11
12
|
sessions = new Map();
|
|
12
13
|
entries = new Map(); // Stores a list of session entries per each session by its linearAgentActivitySessionId
|
|
13
14
|
activeTasksBySession = new Map(); // Maps session ID to active Task tool use ID
|
|
@@ -17,12 +18,11 @@ export class AgentSessionManager {
|
|
|
17
18
|
sharedApplicationServer;
|
|
18
19
|
getParentSessionId;
|
|
19
20
|
resumeParentSession;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
this.
|
|
21
|
+
constructor(issueTracker, getParentSessionId, resumeParentSession, procedureRouter, sharedApplicationServer) {
|
|
22
|
+
super();
|
|
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
|
}
|
|
@@ -34,9 +34,9 @@ export class AgentSessionManager {
|
|
|
34
34
|
console.log(`[AgentSessionManager] Tracking Linear session ${linearAgentActivitySessionId} for issue ${issueId}`);
|
|
35
35
|
const agentSession = {
|
|
36
36
|
linearAgentActivitySessionId,
|
|
37
|
-
type:
|
|
38
|
-
status:
|
|
39
|
-
context:
|
|
37
|
+
type: AgentSessionType.CommentThread,
|
|
38
|
+
status: AgentSessionStatus.Active,
|
|
39
|
+
context: AgentSessionType.CommentThread,
|
|
40
40
|
createdAt: Date.now(),
|
|
41
41
|
updatedAt: Date.now(),
|
|
42
42
|
issueId,
|
|
@@ -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,352 +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 any extra whitespace
|
|
317
|
-
cleanedResult = cleanedResult.trim();
|
|
318
|
-
// Try to detect language from file extension
|
|
319
|
-
let lang = "";
|
|
320
|
-
if (toolInput.file_path) {
|
|
321
|
-
const ext = toolInput.file_path.split(".").pop()?.toLowerCase();
|
|
322
|
-
const langMap = {
|
|
323
|
-
ts: "typescript",
|
|
324
|
-
tsx: "typescript",
|
|
325
|
-
js: "javascript",
|
|
326
|
-
jsx: "javascript",
|
|
327
|
-
py: "python",
|
|
328
|
-
rb: "ruby",
|
|
329
|
-
go: "go",
|
|
330
|
-
rs: "rust",
|
|
331
|
-
java: "java",
|
|
332
|
-
c: "c",
|
|
333
|
-
cpp: "cpp",
|
|
334
|
-
cs: "csharp",
|
|
335
|
-
php: "php",
|
|
336
|
-
swift: "swift",
|
|
337
|
-
kt: "kotlin",
|
|
338
|
-
scala: "scala",
|
|
339
|
-
sh: "bash",
|
|
340
|
-
bash: "bash",
|
|
341
|
-
zsh: "bash",
|
|
342
|
-
yml: "yaml",
|
|
343
|
-
yaml: "yaml",
|
|
344
|
-
json: "json",
|
|
345
|
-
xml: "xml",
|
|
346
|
-
html: "html",
|
|
347
|
-
css: "css",
|
|
348
|
-
scss: "scss",
|
|
349
|
-
md: "markdown",
|
|
350
|
-
sql: "sql",
|
|
351
|
-
};
|
|
352
|
-
lang = langMap[ext || ""] || "";
|
|
353
|
-
}
|
|
354
|
-
return `\`\`\`${lang}\n${cleanedResult}\n\`\`\``;
|
|
355
|
-
}
|
|
356
|
-
return "*Empty file*";
|
|
357
|
-
case "Edit":
|
|
358
|
-
case "↪ Edit": {
|
|
359
|
-
// For Edit, show changes as a diff
|
|
360
|
-
// Extract old_string and new_string from toolInput
|
|
361
|
-
if (toolInput.old_string && toolInput.new_string) {
|
|
362
|
-
// Format as a unified diff
|
|
363
|
-
const oldLines = toolInput.old_string.split("\n");
|
|
364
|
-
const newLines = toolInput.new_string.split("\n");
|
|
365
|
-
let diff = "```diff\n";
|
|
366
|
-
// Add context lines before changes (show all old lines with - prefix)
|
|
367
|
-
for (const line of oldLines) {
|
|
368
|
-
diff += `-${line}\n`;
|
|
369
|
-
}
|
|
370
|
-
// Add new lines with + prefix
|
|
371
|
-
for (const line of newLines) {
|
|
372
|
-
diff += `+${line}\n`;
|
|
373
|
-
}
|
|
374
|
-
diff += "```";
|
|
375
|
-
return diff;
|
|
376
|
-
}
|
|
377
|
-
// Fallback to result if old/new strings not available
|
|
378
|
-
if (result?.trim()) {
|
|
379
|
-
return result;
|
|
380
|
-
}
|
|
381
|
-
return "*Edit completed*";
|
|
382
|
-
}
|
|
383
|
-
case "Write":
|
|
384
|
-
case "↪ Write":
|
|
385
|
-
// For Write, just confirm
|
|
386
|
-
if (result?.trim()) {
|
|
387
|
-
return result; // In case there's an error or message
|
|
388
|
-
}
|
|
389
|
-
return "*File written successfully*";
|
|
390
|
-
case "Grep":
|
|
391
|
-
case "↪ Grep": {
|
|
392
|
-
// Format grep results
|
|
393
|
-
if (result?.trim()) {
|
|
394
|
-
const lines = result.split("\n");
|
|
395
|
-
// If it looks like file paths (files_with_matches mode)
|
|
396
|
-
if (lines.length > 0 &&
|
|
397
|
-
lines[0] &&
|
|
398
|
-
!lines[0].includes(":") &&
|
|
399
|
-
lines[0].trim().length > 0) {
|
|
400
|
-
return `Found ${lines.filter((l) => l.trim()).length} matching files:\n\`\`\`\n${result}\n\`\`\``;
|
|
401
|
-
}
|
|
402
|
-
// Otherwise it's content matches
|
|
403
|
-
return `\`\`\`\n${result}\n\`\`\``;
|
|
404
|
-
}
|
|
405
|
-
return "*No matches found*";
|
|
406
|
-
}
|
|
407
|
-
case "Glob":
|
|
408
|
-
case "↪ Glob": {
|
|
409
|
-
if (result?.trim()) {
|
|
410
|
-
const lines = result.split("\n").filter((l) => l.trim());
|
|
411
|
-
return `Found ${lines.length} matching files:\n\`\`\`\n${result}\n\`\`\``;
|
|
412
|
-
}
|
|
413
|
-
return "*No files found*";
|
|
414
|
-
}
|
|
415
|
-
case "Task":
|
|
416
|
-
case "↪ Task":
|
|
417
|
-
// Task results can be complex - keep as is but in code block if multiline
|
|
418
|
-
if (result?.trim()) {
|
|
419
|
-
if (result.includes("\n")) {
|
|
420
|
-
return `\`\`\`\n${result}\n\`\`\``;
|
|
421
|
-
}
|
|
422
|
-
return result;
|
|
423
|
-
}
|
|
424
|
-
return "*Task completed*";
|
|
425
|
-
case "WebFetch":
|
|
426
|
-
case "↪ WebFetch":
|
|
427
|
-
case "WebSearch":
|
|
428
|
-
case "↪ WebSearch":
|
|
429
|
-
// Web results are usually formatted, keep as is
|
|
430
|
-
return result || "*No results*";
|
|
431
|
-
default:
|
|
432
|
-
// For unknown tools, use code block if result has multiple lines
|
|
433
|
-
if (result?.trim()) {
|
|
434
|
-
if (result.includes("\n") && result.length > 100) {
|
|
435
|
-
return `\`\`\`\n${result}\n\`\`\``;
|
|
436
|
-
}
|
|
437
|
-
return result;
|
|
438
|
-
}
|
|
439
|
-
return "*Completed*";
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
catch (error) {
|
|
443
|
-
console.error("[AgentSessionManager] Failed to format tool result:", error);
|
|
444
|
-
return result || "";
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
118
|
/**
|
|
448
119
|
* Complete a session from Claude result message
|
|
449
120
|
*/
|
|
@@ -459,8 +130,8 @@ export class AgentSessionManager {
|
|
|
459
130
|
// Note: We should ideally track by session, but for now clearing all is safer
|
|
460
131
|
// to prevent memory leaks
|
|
461
132
|
const status = resultMessage.subtype === "success"
|
|
462
|
-
?
|
|
463
|
-
:
|
|
133
|
+
? AgentSessionStatus.Complete
|
|
134
|
+
: AgentSessionStatus.Error;
|
|
464
135
|
// Update session status and metadata
|
|
465
136
|
await this.updateSessionStatus(linearAgentActivitySessionId, status, {
|
|
466
137
|
totalCostUsd: resultMessage.total_cost_usd,
|
|
@@ -483,9 +154,10 @@ export class AgentSessionManager {
|
|
|
483
154
|
console.log(`[AgentSessionManager] Subroutine completed with error, not triggering next subroutine`);
|
|
484
155
|
return;
|
|
485
156
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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`);
|
|
489
161
|
return;
|
|
490
162
|
}
|
|
491
163
|
// Check if there's a next subroutine
|
|
@@ -546,16 +218,13 @@ export class AgentSessionManager {
|
|
|
546
218
|
}
|
|
547
219
|
// Advance procedure state
|
|
548
220
|
console.log(`[AgentSessionManager] Subroutine completed, advancing to next: ${nextSubroutine.name}`);
|
|
549
|
-
this.procedureRouter.advanceToNextSubroutine(session,
|
|
550
|
-
//
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
console.error(`[AgentSessionManager] Failed to trigger next subroutine:`, error);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
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
|
+
});
|
|
559
228
|
}
|
|
560
229
|
else {
|
|
561
230
|
// Procedure complete - post final result
|
|
@@ -633,7 +302,7 @@ export class AgentSessionManager {
|
|
|
633
302
|
catch (error) {
|
|
634
303
|
console.error(`[AgentSessionManager] Error handling message:`, error);
|
|
635
304
|
// Mark session as error state
|
|
636
|
-
await this.updateSessionStatus(linearAgentActivitySessionId,
|
|
305
|
+
await this.updateSessionStatus(linearAgentActivitySessionId, AgentSessionStatus.Error);
|
|
637
306
|
}
|
|
638
307
|
}
|
|
639
308
|
/**
|
|
@@ -651,11 +320,18 @@ export class AgentSessionManager {
|
|
|
651
320
|
this.sessions.set(linearAgentActivitySessionId, session);
|
|
652
321
|
}
|
|
653
322
|
/**
|
|
654
|
-
* Add result entry from
|
|
323
|
+
* Add result entry from result message
|
|
655
324
|
*/
|
|
656
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";
|
|
657
330
|
const resultEntry = {
|
|
658
|
-
|
|
331
|
+
// Set the appropriate session ID based on runner type
|
|
332
|
+
...(isGeminiRunner
|
|
333
|
+
? { geminiSessionId: resultMessage.session_id }
|
|
334
|
+
: { claudeSessionId: resultMessage.session_id }),
|
|
659
335
|
type: "result",
|
|
660
336
|
content: "result" in resultMessage ? resultMessage.result : "",
|
|
661
337
|
metadata: {
|
|
@@ -798,15 +474,23 @@ export class AgentSessionManager {
|
|
|
798
474
|
if (entry.metadata.toolUseId) {
|
|
799
475
|
this.toolCallsByToolUseId.delete(entry.metadata.toolUseId);
|
|
800
476
|
}
|
|
801
|
-
// Skip creating activity for TodoWrite results since
|
|
802
|
-
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}`);
|
|
803
487
|
return;
|
|
804
488
|
}
|
|
805
|
-
// Format parameter and result using
|
|
806
|
-
const formattedParameter =
|
|
807
|
-
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);
|
|
808
492
|
// Format the action name (with description for Bash tool)
|
|
809
|
-
const formattedAction =
|
|
493
|
+
const formattedAction = formatter.formatToolActionName(toolName, toolInput, toolResult.isError);
|
|
810
494
|
content = {
|
|
811
495
|
type: "action",
|
|
812
496
|
action: formattedAction,
|
|
@@ -842,20 +526,32 @@ export class AgentSessionManager {
|
|
|
842
526
|
input: entry.metadata.toolInput || entry.content,
|
|
843
527
|
});
|
|
844
528
|
}
|
|
845
|
-
// Special handling for TodoWrite tool - treat as thought instead of action
|
|
846
|
-
if (toolName === "TodoWrite") {
|
|
847
|
-
|
|
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);
|
|
848
538
|
content = {
|
|
849
539
|
type: "thought",
|
|
850
540
|
body: formattedTodos,
|
|
851
541
|
};
|
|
852
|
-
// TodoWrite is not ephemeral
|
|
542
|
+
// TodoWrite/write_todos is not ephemeral
|
|
853
543
|
ephemeral = false;
|
|
854
544
|
}
|
|
855
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
|
+
}
|
|
856
552
|
// Special handling for Task tool - add start marker and track active task
|
|
857
553
|
const toolInput = entry.metadata.toolInput || entry.content;
|
|
858
|
-
const formattedParameter =
|
|
554
|
+
const formattedParameter = formatter.formatToolParameter(toolName, toolInput);
|
|
859
555
|
const displayName = toolName;
|
|
860
556
|
// Track this as the active Task for this session
|
|
861
557
|
if (entry.metadata?.toolUseId) {
|
|
@@ -871,6 +567,12 @@ export class AgentSessionManager {
|
|
|
871
567
|
ephemeral = false;
|
|
872
568
|
}
|
|
873
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
|
+
}
|
|
874
576
|
// Other tools - check if they're within an active Task
|
|
875
577
|
const toolInput = entry.metadata.toolInput || entry.content;
|
|
876
578
|
let displayName = toolName;
|
|
@@ -880,7 +582,7 @@ export class AgentSessionManager {
|
|
|
880
582
|
displayName = `↪ ${toolName}`;
|
|
881
583
|
}
|
|
882
584
|
}
|
|
883
|
-
const formattedParameter =
|
|
585
|
+
const formattedParameter = formatter.formatToolParameter(displayName, toolInput);
|
|
884
586
|
content = {
|
|
885
587
|
type: "action",
|
|
886
588
|
action: displayName,
|
|
@@ -944,7 +646,7 @@ export class AgentSessionManager {
|
|
|
944
646
|
content,
|
|
945
647
|
...(ephemeral && { ephemeral: true }),
|
|
946
648
|
};
|
|
947
|
-
const result = await this.
|
|
649
|
+
const result = await this.issueTracker.createAgentActivity(activityInput);
|
|
948
650
|
if (result.success && result.agentActivity) {
|
|
949
651
|
const agentActivity = await result.agentActivity;
|
|
950
652
|
entry.linearAgentActivityId = agentActivity.id;
|
|
@@ -974,36 +676,36 @@ export class AgentSessionManager {
|
|
|
974
676
|
* Get all active sessions
|
|
975
677
|
*/
|
|
976
678
|
getActiveSessions() {
|
|
977
|
-
return Array.from(this.sessions.values()).filter((session) => session.status ===
|
|
679
|
+
return Array.from(this.sessions.values()).filter((session) => session.status === AgentSessionStatus.Active);
|
|
978
680
|
}
|
|
979
681
|
/**
|
|
980
|
-
* Add or update
|
|
682
|
+
* Add or update agent runner for a session
|
|
981
683
|
*/
|
|
982
|
-
|
|
684
|
+
addAgentRunner(linearAgentActivitySessionId, agentRunner) {
|
|
983
685
|
const session = this.sessions.get(linearAgentActivitySessionId);
|
|
984
686
|
if (!session) {
|
|
985
687
|
console.warn(`[AgentSessionManager] No session found for linearAgentActivitySessionId ${linearAgentActivitySessionId}`);
|
|
986
688
|
return;
|
|
987
689
|
}
|
|
988
|
-
session.
|
|
690
|
+
session.agentRunner = agentRunner;
|
|
989
691
|
session.updatedAt = Date.now();
|
|
990
|
-
console.log(`[AgentSessionManager] Added
|
|
692
|
+
console.log(`[AgentSessionManager] Added agent runner to session ${linearAgentActivitySessionId}`);
|
|
991
693
|
}
|
|
992
694
|
/**
|
|
993
|
-
* Get all
|
|
695
|
+
* Get all agent runners
|
|
994
696
|
*/
|
|
995
|
-
|
|
697
|
+
getAllAgentRunners() {
|
|
996
698
|
return Array.from(this.sessions.values())
|
|
997
|
-
.map((session) => session.
|
|
699
|
+
.map((session) => session.agentRunner)
|
|
998
700
|
.filter((runner) => runner !== undefined);
|
|
999
701
|
}
|
|
1000
702
|
/**
|
|
1001
|
-
* Get all
|
|
703
|
+
* Get all agent runners for a specific issue
|
|
1002
704
|
*/
|
|
1003
|
-
|
|
705
|
+
getAgentRunnersForIssue(issueId) {
|
|
1004
706
|
return Array.from(this.sessions.values())
|
|
1005
707
|
.filter((session) => session.issueId === issueId)
|
|
1006
|
-
.map((session) => session.
|
|
708
|
+
.map((session) => session.agentRunner)
|
|
1007
709
|
.filter((runner) => runner !== undefined);
|
|
1008
710
|
}
|
|
1009
711
|
/**
|
|
@@ -1017,7 +719,7 @@ export class AgentSessionManager {
|
|
|
1017
719
|
*/
|
|
1018
720
|
getActiveSessionsByIssueId(issueId) {
|
|
1019
721
|
return Array.from(this.sessions.values()).filter((session) => session.issueId === issueId &&
|
|
1020
|
-
session.status ===
|
|
722
|
+
session.status === AgentSessionStatus.Active);
|
|
1021
723
|
}
|
|
1022
724
|
/**
|
|
1023
725
|
* Get all sessions
|
|
@@ -1026,18 +728,18 @@ export class AgentSessionManager {
|
|
|
1026
728
|
return Array.from(this.sessions.values());
|
|
1027
729
|
}
|
|
1028
730
|
/**
|
|
1029
|
-
* Get
|
|
731
|
+
* Get agent runner for a specific session
|
|
1030
732
|
*/
|
|
1031
|
-
|
|
733
|
+
getAgentRunner(linearAgentActivitySessionId) {
|
|
1032
734
|
const session = this.sessions.get(linearAgentActivitySessionId);
|
|
1033
|
-
return session?.
|
|
735
|
+
return session?.agentRunner;
|
|
1034
736
|
}
|
|
1035
737
|
/**
|
|
1036
|
-
* Check if
|
|
738
|
+
* Check if an agent runner exists for a session
|
|
1037
739
|
*/
|
|
1038
|
-
|
|
740
|
+
hasAgentRunner(linearAgentActivitySessionId) {
|
|
1039
741
|
const session = this.sessions.get(linearAgentActivitySessionId);
|
|
1040
|
-
return session?.
|
|
742
|
+
return session?.agentRunner !== undefined;
|
|
1041
743
|
}
|
|
1042
744
|
/**
|
|
1043
745
|
* Create a thought activity
|
|
@@ -1049,7 +751,7 @@ export class AgentSessionManager {
|
|
|
1049
751
|
return;
|
|
1050
752
|
}
|
|
1051
753
|
try {
|
|
1052
|
-
const result = await this.
|
|
754
|
+
const result = await this.issueTracker.createAgentActivity({
|
|
1053
755
|
agentSessionId: session.linearAgentActivitySessionId,
|
|
1054
756
|
content: {
|
|
1055
757
|
type: "thought",
|
|
@@ -1085,7 +787,7 @@ export class AgentSessionManager {
|
|
|
1085
787
|
if (result !== undefined) {
|
|
1086
788
|
content.result = result;
|
|
1087
789
|
}
|
|
1088
|
-
const response = await this.
|
|
790
|
+
const response = await this.issueTracker.createAgentActivity({
|
|
1089
791
|
agentSessionId: session.linearAgentActivitySessionId,
|
|
1090
792
|
content,
|
|
1091
793
|
});
|
|
@@ -1110,7 +812,7 @@ export class AgentSessionManager {
|
|
|
1110
812
|
return;
|
|
1111
813
|
}
|
|
1112
814
|
try {
|
|
1113
|
-
const result = await this.
|
|
815
|
+
const result = await this.issueTracker.createAgentActivity({
|
|
1114
816
|
agentSessionId: session.linearAgentActivitySessionId,
|
|
1115
817
|
content: {
|
|
1116
818
|
type: "response",
|
|
@@ -1138,7 +840,7 @@ export class AgentSessionManager {
|
|
|
1138
840
|
return;
|
|
1139
841
|
}
|
|
1140
842
|
try {
|
|
1141
|
-
const result = await this.
|
|
843
|
+
const result = await this.issueTracker.createAgentActivity({
|
|
1142
844
|
agentSessionId: session.linearAgentActivitySessionId,
|
|
1143
845
|
content: {
|
|
1144
846
|
type: "error",
|
|
@@ -1166,7 +868,7 @@ export class AgentSessionManager {
|
|
|
1166
868
|
return;
|
|
1167
869
|
}
|
|
1168
870
|
try {
|
|
1169
|
-
const result = await this.
|
|
871
|
+
const result = await this.issueTracker.createAgentActivity({
|
|
1170
872
|
agentSessionId: session.linearAgentActivitySessionId,
|
|
1171
873
|
content: {
|
|
1172
874
|
type: "elicitation",
|
|
@@ -1194,13 +896,13 @@ export class AgentSessionManager {
|
|
|
1194
896
|
return;
|
|
1195
897
|
}
|
|
1196
898
|
try {
|
|
1197
|
-
const result = await this.
|
|
899
|
+
const result = await this.issueTracker.createAgentActivity({
|
|
1198
900
|
agentSessionId: session.linearAgentActivitySessionId,
|
|
1199
901
|
content: {
|
|
1200
902
|
type: "elicitation",
|
|
1201
903
|
body,
|
|
1202
904
|
},
|
|
1203
|
-
signal:
|
|
905
|
+
signal: AgentActivitySignal.Auth,
|
|
1204
906
|
signalMetadata: {
|
|
1205
907
|
url: approvalUrl,
|
|
1206
908
|
},
|
|
@@ -1238,8 +940,8 @@ export class AgentSessionManager {
|
|
|
1238
940
|
const entries = {};
|
|
1239
941
|
// Serialize sessions
|
|
1240
942
|
for (const [sessionId, session] of this.sessions.entries()) {
|
|
1241
|
-
// Exclude
|
|
1242
|
-
const {
|
|
943
|
+
// Exclude agentRunner from serialization as it's not serializable
|
|
944
|
+
const { agentRunner: _agentRunner, ...serializableSession } = session;
|
|
1243
945
|
sessions[sessionId] = serializableSession;
|
|
1244
946
|
}
|
|
1245
947
|
// Serialize entries
|
|
@@ -1278,7 +980,7 @@ export class AgentSessionManager {
|
|
|
1278
980
|
*/
|
|
1279
981
|
async postModelNotificationThought(linearAgentActivitySessionId, model) {
|
|
1280
982
|
try {
|
|
1281
|
-
const result = await this.
|
|
983
|
+
const result = await this.issueTracker.createAgentActivity({
|
|
1282
984
|
agentSessionId: linearAgentActivitySessionId,
|
|
1283
985
|
content: {
|
|
1284
986
|
type: "thought",
|
|
@@ -1301,7 +1003,7 @@ export class AgentSessionManager {
|
|
|
1301
1003
|
*/
|
|
1302
1004
|
async postRoutingThought(linearAgentActivitySessionId) {
|
|
1303
1005
|
try {
|
|
1304
|
-
const result = await this.
|
|
1006
|
+
const result = await this.issueTracker.createAgentActivity({
|
|
1305
1007
|
agentSessionId: linearAgentActivitySessionId,
|
|
1306
1008
|
content: {
|
|
1307
1009
|
type: "thought",
|
|
@@ -1329,7 +1031,7 @@ export class AgentSessionManager {
|
|
|
1329
1031
|
*/
|
|
1330
1032
|
async postProcedureSelectionThought(linearAgentActivitySessionId, procedureName, classification) {
|
|
1331
1033
|
try {
|
|
1332
|
-
const result = await this.
|
|
1034
|
+
const result = await this.issueTracker.createAgentActivity({
|
|
1333
1035
|
agentSessionId: linearAgentActivitySessionId,
|
|
1334
1036
|
content: {
|
|
1335
1037
|
type: "thought",
|
|
@@ -1360,7 +1062,7 @@ export class AgentSessionManager {
|
|
|
1360
1062
|
try {
|
|
1361
1063
|
if (message.status === "compacting") {
|
|
1362
1064
|
// Create an ephemeral thought for the compacting status
|
|
1363
|
-
const result = await this.
|
|
1065
|
+
const result = await this.issueTracker.createAgentActivity({
|
|
1364
1066
|
agentSessionId: session.linearAgentActivitySessionId,
|
|
1365
1067
|
content: {
|
|
1366
1068
|
type: "thought",
|
|
@@ -1380,7 +1082,7 @@ export class AgentSessionManager {
|
|
|
1380
1082
|
}
|
|
1381
1083
|
else if (message.status === null) {
|
|
1382
1084
|
// Clear the status - post a non-ephemeral thought to replace the ephemeral one
|
|
1383
|
-
const result = await this.
|
|
1085
|
+
const result = await this.issueTracker.createAgentActivity({
|
|
1384
1086
|
agentSessionId: session.linearAgentActivitySessionId,
|
|
1385
1087
|
content: {
|
|
1386
1088
|
type: "thought",
|