cyrus-edge-worker 0.2.21 → 0.2.22
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/ActivityPoster.d.ts +15 -0
- package/dist/ActivityPoster.d.ts.map +1 -0
- package/dist/ActivityPoster.js +194 -0
- package/dist/ActivityPoster.js.map +1 -0
- package/dist/AgentSessionManager.d.ts +63 -21
- package/dist/AgentSessionManager.d.ts.map +1 -1
- package/dist/AgentSessionManager.js +413 -404
- package/dist/AgentSessionManager.js.map +1 -1
- package/dist/AskUserQuestionHandler.d.ts +3 -2
- package/dist/AskUserQuestionHandler.d.ts.map +1 -1
- package/dist/AskUserQuestionHandler.js +15 -12
- package/dist/AskUserQuestionHandler.js.map +1 -1
- package/dist/AttachmentService.d.ts +69 -0
- package/dist/AttachmentService.d.ts.map +1 -0
- package/dist/AttachmentService.js +369 -0
- package/dist/AttachmentService.js.map +1 -0
- package/dist/ChatSessionHandler.d.ts +87 -0
- package/dist/ChatSessionHandler.d.ts.map +1 -0
- package/dist/ChatSessionHandler.js +227 -0
- package/dist/ChatSessionHandler.js.map +1 -0
- package/dist/ConfigManager.d.ts +91 -0
- package/dist/ConfigManager.d.ts.map +1 -0
- package/dist/ConfigManager.js +227 -0
- package/dist/ConfigManager.js.map +1 -0
- package/dist/EdgeWorker.d.ts +152 -120
- package/dist/EdgeWorker.d.ts.map +1 -1
- package/dist/EdgeWorker.js +1333 -2125
- package/dist/EdgeWorker.js.map +1 -1
- package/dist/GitService.d.ts +14 -10
- package/dist/GitService.d.ts.map +1 -1
- package/dist/GitService.js +91 -12
- package/dist/GitService.js.map +1 -1
- package/dist/GlobalSessionRegistry.d.ts +142 -0
- package/dist/GlobalSessionRegistry.d.ts.map +1 -0
- package/dist/GlobalSessionRegistry.js +254 -0
- package/dist/GlobalSessionRegistry.js.map +1 -0
- package/dist/PromptBuilder.d.ts +175 -0
- package/dist/PromptBuilder.d.ts.map +1 -0
- package/dist/PromptBuilder.js +884 -0
- package/dist/PromptBuilder.js.map +1 -0
- package/dist/RepositoryRouter.d.ts +3 -2
- package/dist/RepositoryRouter.d.ts.map +1 -1
- package/dist/RepositoryRouter.js +39 -37
- package/dist/RepositoryRouter.js.map +1 -1
- package/dist/RunnerSelectionService.d.ts +71 -0
- package/dist/RunnerSelectionService.d.ts.map +1 -0
- package/dist/RunnerSelectionService.js +392 -0
- package/dist/RunnerSelectionService.js.map +1 -0
- package/dist/SharedApplicationServer.d.ts +3 -1
- package/dist/SharedApplicationServer.d.ts.map +1 -1
- package/dist/SharedApplicationServer.js +21 -17
- package/dist/SharedApplicationServer.js.map +1 -1
- package/dist/SharedWebhookServer.d.ts +3 -1
- package/dist/SharedWebhookServer.d.ts.map +1 -1
- package/dist/SharedWebhookServer.js +19 -16
- package/dist/SharedWebhookServer.js.map +1 -1
- package/dist/SlackChatAdapter.d.ts +25 -0
- package/dist/SlackChatAdapter.d.ts.map +1 -0
- package/dist/SlackChatAdapter.js +143 -0
- package/dist/SlackChatAdapter.js.map +1 -0
- package/dist/WorktreeIncludeService.d.ts +2 -9
- package/dist/WorktreeIncludeService.d.ts.map +1 -1
- package/dist/WorktreeIncludeService.js +3 -9
- package/dist/WorktreeIncludeService.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/procedures/ProcedureAnalyzer.d.ts +6 -4
- package/dist/procedures/ProcedureAnalyzer.d.ts.map +1 -1
- package/dist/procedures/ProcedureAnalyzer.js +32 -12
- package/dist/procedures/ProcedureAnalyzer.js.map +1 -1
- package/dist/procedures/types.d.ts +1 -0
- package/dist/procedures/types.d.ts.map +1 -1
- package/dist/prompts/graphite-orchestrator.md +4 -2
- package/dist/prompts/orchestrator.md +5 -3
- package/dist/sinks/IActivitySink.d.ts +60 -0
- package/dist/sinks/IActivitySink.d.ts.map +1 -0
- package/dist/sinks/IActivitySink.js +2 -0
- package/dist/sinks/IActivitySink.js.map +1 -0
- package/dist/sinks/LinearActivitySink.d.ts +69 -0
- package/dist/sinks/LinearActivitySink.d.ts.map +1 -0
- package/dist/sinks/LinearActivitySink.js +111 -0
- package/dist/sinks/LinearActivitySink.js.map +1 -0
- package/dist/sinks/NoopActivitySink.d.ts +13 -0
- package/dist/sinks/NoopActivitySink.d.ts.map +1 -0
- package/dist/sinks/NoopActivitySink.js +17 -0
- package/dist/sinks/NoopActivitySink.js.map +1 -0
- package/dist/sinks/index.d.ts +9 -0
- package/dist/sinks/index.d.ts.map +1 -0
- package/dist/sinks/index.js +8 -0
- package/dist/sinks/index.js.map +1 -0
- package/package.json +16 -10
- package/prompts/graphite-orchestrator.md +4 -2
- package/prompts/orchestrator.md +5 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
|
-
import {
|
|
2
|
+
import { AgentSessionStatus, AgentSessionType, createLogger, } from "cyrus-core";
|
|
3
3
|
import { DEFAULT_VALIDATION_LOOP_CONFIG, parseValidationResult, renderValidationFixerPrompt, } from "./validation/index.js";
|
|
4
4
|
/**
|
|
5
5
|
* Manages Agent Sessions integration with Claude Code SDK
|
|
@@ -9,65 +9,135 @@ import { DEFAULT_VALIDATION_LOOP_CONFIG, parseValidationResult, renderValidation
|
|
|
9
9
|
* CURRENTLY BEING HANDLED 'per repository'
|
|
10
10
|
*/
|
|
11
11
|
export class AgentSessionManager extends EventEmitter {
|
|
12
|
-
|
|
12
|
+
logger;
|
|
13
|
+
activitySink;
|
|
13
14
|
sessions = new Map();
|
|
14
|
-
entries = new Map(); // Stores a list of session entries per each session by its
|
|
15
|
+
entries = new Map(); // Stores a list of session entries per each session by its id
|
|
15
16
|
activeTasksBySession = new Map(); // Maps session ID to active Task tool use ID
|
|
16
17
|
toolCallsByToolUseId = new Map(); // Track tool calls by their tool_use_id
|
|
17
18
|
taskSubjectsByToolUseId = new Map(); // Cache TaskCreate subjects by toolUseId until result arrives with task ID
|
|
18
19
|
taskSubjectsById = new Map(); // Cache task subjects by task ID (e.g., "1" → "Fix login bug")
|
|
19
20
|
activeStatusActivitiesBySession = new Map(); // Maps session ID to active compacting status activity ID
|
|
21
|
+
stopRequestedSessions = new Set(); // Sessions explicitly stopped by user signal
|
|
20
22
|
procedureAnalyzer;
|
|
21
23
|
sharedApplicationServer;
|
|
22
24
|
getParentSessionId;
|
|
23
25
|
resumeParentSession;
|
|
24
|
-
constructor(
|
|
26
|
+
constructor(activitySink, getParentSessionId, resumeParentSession, procedureAnalyzer, sharedApplicationServer, logger) {
|
|
25
27
|
super();
|
|
26
|
-
this.
|
|
28
|
+
this.logger = logger ?? createLogger({ component: "AgentSessionManager" });
|
|
29
|
+
this.activitySink = activitySink;
|
|
27
30
|
this.getParentSessionId = getParentSessionId;
|
|
28
31
|
this.resumeParentSession = resumeParentSession;
|
|
29
32
|
this.procedureAnalyzer = procedureAnalyzer;
|
|
30
33
|
this.sharedApplicationServer = sharedApplicationServer;
|
|
31
34
|
}
|
|
32
35
|
/**
|
|
33
|
-
*
|
|
34
|
-
* The session is already created by Linear, we just need to track it
|
|
36
|
+
* Get a session-scoped logger with context (sessionId, platform, issueIdentifier).
|
|
35
37
|
*/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
sessionLog(sessionId) {
|
|
39
|
+
const session = this.sessions.get(sessionId);
|
|
40
|
+
return this.logger.withContext({
|
|
41
|
+
sessionId,
|
|
42
|
+
platform: session?.issueContext?.trackerId,
|
|
43
|
+
issueIdentifier: session?.issueContext?.issueIdentifier,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Initialize an agent session from webhook
|
|
48
|
+
* The session is already created by the platform, we just need to track it
|
|
49
|
+
*
|
|
50
|
+
* @param sessionId - Internal session ID
|
|
51
|
+
* @param issueId - Issue/PR identifier
|
|
52
|
+
* @param issueMinimal - Minimal issue data
|
|
53
|
+
* @param workspace - Workspace configuration
|
|
54
|
+
* @param platform - Source platform ("linear", "github", "slack"). Defaults to "linear".
|
|
55
|
+
* Only "linear" sessions will have activities streamed to Linear.
|
|
56
|
+
*/
|
|
57
|
+
createLinearAgentSession(sessionId, issueId, issueMinimal, workspace, platform = "linear") {
|
|
58
|
+
const log = this.logger.withContext({
|
|
59
|
+
sessionId,
|
|
60
|
+
platform,
|
|
61
|
+
issueIdentifier: issueMinimal.identifier,
|
|
62
|
+
});
|
|
63
|
+
log.info(`Tracking session for issue ${issueId}`);
|
|
38
64
|
const agentSession = {
|
|
39
|
-
|
|
65
|
+
id: sessionId,
|
|
66
|
+
// Only Linear sessions have a valid external session ID for posting activities
|
|
67
|
+
externalSessionId: platform === "linear" ? sessionId : undefined,
|
|
40
68
|
type: AgentSessionType.CommentThread,
|
|
41
69
|
status: AgentSessionStatus.Active,
|
|
42
70
|
context: AgentSessionType.CommentThread,
|
|
43
71
|
createdAt: Date.now(),
|
|
44
72
|
updatedAt: Date.now(),
|
|
45
|
-
|
|
73
|
+
issueContext: {
|
|
74
|
+
trackerId: platform,
|
|
75
|
+
issueId: issueId,
|
|
76
|
+
issueIdentifier: issueMinimal.identifier,
|
|
77
|
+
},
|
|
78
|
+
issueId, // Kept for backwards compatibility
|
|
46
79
|
issue: issueMinimal,
|
|
47
80
|
workspace: workspace,
|
|
48
81
|
};
|
|
49
82
|
// Store locally
|
|
50
|
-
this.sessions.set(
|
|
51
|
-
this.entries.set(
|
|
83
|
+
this.sessions.set(sessionId, agentSession);
|
|
84
|
+
this.entries.set(sessionId, []);
|
|
85
|
+
return agentSession;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Create an agent session for chat-style platforms (Slack, etc.) that are
|
|
89
|
+
* not tied to a specific issue or repository.
|
|
90
|
+
*
|
|
91
|
+
* Unlike {@link createLinearAgentSession}, this does NOT require issue
|
|
92
|
+
* context — the session lives in a standalone workspace with no issue
|
|
93
|
+
* tracker linkage.
|
|
94
|
+
*/
|
|
95
|
+
createChatSession(sessionId, workspace, platform) {
|
|
96
|
+
const log = this.logger.withContext({ sessionId, platform });
|
|
97
|
+
log.info("Creating chat session");
|
|
98
|
+
const agentSession = {
|
|
99
|
+
id: sessionId,
|
|
100
|
+
type: AgentSessionType.CommentThread,
|
|
101
|
+
status: AgentSessionStatus.Active,
|
|
102
|
+
context: AgentSessionType.CommentThread,
|
|
103
|
+
createdAt: Date.now(),
|
|
104
|
+
updatedAt: Date.now(),
|
|
105
|
+
workspace,
|
|
106
|
+
};
|
|
107
|
+
this.sessions.set(sessionId, agentSession);
|
|
108
|
+
this.entries.set(sessionId, []);
|
|
52
109
|
return agentSession;
|
|
53
110
|
}
|
|
54
111
|
/**
|
|
55
112
|
* Update Agent Session with session ID from system initialization
|
|
56
113
|
* Automatically detects whether it's Claude or Gemini based on the runner
|
|
57
114
|
*/
|
|
58
|
-
updateAgentSessionWithClaudeSessionId(
|
|
59
|
-
const linearSession = this.sessions.get(
|
|
115
|
+
updateAgentSessionWithClaudeSessionId(sessionId, claudeSystemMessage) {
|
|
116
|
+
const linearSession = this.sessions.get(sessionId);
|
|
60
117
|
if (!linearSession) {
|
|
61
|
-
|
|
118
|
+
const log = this.sessionLog(sessionId);
|
|
119
|
+
log.warn(`No session found`);
|
|
62
120
|
return;
|
|
63
121
|
}
|
|
64
122
|
// Determine which runner is being used
|
|
65
123
|
const runner = linearSession.agentRunner;
|
|
66
|
-
const
|
|
124
|
+
const runnerType = runner?.constructor.name === "GeminiRunner"
|
|
125
|
+
? "gemini"
|
|
126
|
+
: runner?.constructor.name === "CodexRunner"
|
|
127
|
+
? "codex"
|
|
128
|
+
: runner?.constructor.name === "CursorRunner"
|
|
129
|
+
? "cursor"
|
|
130
|
+
: "claude";
|
|
67
131
|
// Update the appropriate session ID based on runner type
|
|
68
|
-
if (
|
|
132
|
+
if (runnerType === "gemini") {
|
|
69
133
|
linearSession.geminiSessionId = claudeSystemMessage.session_id;
|
|
70
134
|
}
|
|
135
|
+
else if (runnerType === "codex") {
|
|
136
|
+
linearSession.codexSessionId = claudeSystemMessage.session_id;
|
|
137
|
+
}
|
|
138
|
+
else if (runnerType === "cursor") {
|
|
139
|
+
linearSession.cursorSessionId = claudeSystemMessage.session_id;
|
|
140
|
+
}
|
|
71
141
|
else {
|
|
72
142
|
linearSession.claudeSessionId = claudeSystemMessage.session_id;
|
|
73
143
|
}
|
|
@@ -83,7 +153,7 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
83
153
|
/**
|
|
84
154
|
* Create a session entry from user/assistant message (without syncing to Linear)
|
|
85
155
|
*/
|
|
86
|
-
async createSessionEntry(
|
|
156
|
+
async createSessionEntry(sessionId, sdkMessage) {
|
|
87
157
|
// Extract tool info if this is an assistant message
|
|
88
158
|
const toolInfo = sdkMessage.type === "assistant" ? this.extractToolInfo(sdkMessage) : null;
|
|
89
159
|
// Extract tool_use_id and error status if this is a user message with tool_result
|
|
@@ -97,14 +167,24 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
97
167
|
// "error":"rate_limit" field when usage limits are hit
|
|
98
168
|
const sdkError = sdkMessage.type === "assistant" ? sdkMessage.error : undefined;
|
|
99
169
|
// Determine which runner is being used
|
|
100
|
-
const session = this.sessions.get(
|
|
170
|
+
const session = this.sessions.get(sessionId);
|
|
101
171
|
const runner = session?.agentRunner;
|
|
102
|
-
const
|
|
172
|
+
const runnerType = runner?.constructor.name === "GeminiRunner"
|
|
173
|
+
? "gemini"
|
|
174
|
+
: runner?.constructor.name === "CodexRunner"
|
|
175
|
+
? "codex"
|
|
176
|
+
: runner?.constructor.name === "CursorRunner"
|
|
177
|
+
? "cursor"
|
|
178
|
+
: "claude";
|
|
103
179
|
const sessionEntry = {
|
|
104
180
|
// Set the appropriate session ID based on runner type
|
|
105
|
-
...(
|
|
181
|
+
...(runnerType === "gemini"
|
|
106
182
|
? { geminiSessionId: sdkMessage.session_id }
|
|
107
|
-
:
|
|
183
|
+
: runnerType === "codex"
|
|
184
|
+
? { codexSessionId: sdkMessage.session_id }
|
|
185
|
+
: runnerType === "cursor"
|
|
186
|
+
? { cursorSessionId: sdkMessage.session_id }
|
|
187
|
+
: { claudeSessionId: sdkMessage.session_id }),
|
|
108
188
|
type: sdkMessage.type,
|
|
109
189
|
content: this.extractContent(sdkMessage),
|
|
110
190
|
metadata: {
|
|
@@ -128,35 +208,49 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
128
208
|
/**
|
|
129
209
|
* Complete a session from Claude result message
|
|
130
210
|
*/
|
|
131
|
-
async completeSession(
|
|
132
|
-
const session = this.sessions.get(
|
|
211
|
+
async completeSession(sessionId, resultMessage) {
|
|
212
|
+
const session = this.sessions.get(sessionId);
|
|
133
213
|
if (!session) {
|
|
134
|
-
|
|
214
|
+
const log = this.sessionLog(sessionId);
|
|
215
|
+
log.error(`No session found`);
|
|
135
216
|
return;
|
|
136
217
|
}
|
|
218
|
+
const log = this.sessionLog(sessionId);
|
|
137
219
|
// Clear any active Task when session completes
|
|
138
|
-
this.activeTasksBySession.delete(
|
|
220
|
+
this.activeTasksBySession.delete(sessionId);
|
|
139
221
|
// Clear tool calls tracking for this session
|
|
140
222
|
// Note: We should ideally track by session, but for now clearing all is safer
|
|
141
223
|
// to prevent memory leaks
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
224
|
+
const wasStopRequested = this.consumeStopRequest(sessionId);
|
|
225
|
+
const status = wasStopRequested
|
|
226
|
+
? AgentSessionStatus.Error
|
|
227
|
+
: resultMessage.subtype === "success"
|
|
228
|
+
? AgentSessionStatus.Complete
|
|
229
|
+
: AgentSessionStatus.Error;
|
|
145
230
|
// Update session status and metadata
|
|
146
|
-
await this.updateSessionStatus(
|
|
231
|
+
await this.updateSessionStatus(sessionId, status, {
|
|
147
232
|
totalCostUsd: resultMessage.total_cost_usd,
|
|
148
233
|
usage: resultMessage.usage,
|
|
149
234
|
});
|
|
150
|
-
// Handle result using procedure routing system
|
|
235
|
+
// Handle result using procedure routing system (skip for sessions without procedures, e.g. Slack)
|
|
236
|
+
if (!this.procedureAnalyzer) {
|
|
237
|
+
log.info(`Session completed (no procedure routing)`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (wasStopRequested) {
|
|
241
|
+
log.info(`Session ${sessionId} was stopped by user; skipping procedure continuation`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
151
244
|
if ("result" in resultMessage && resultMessage.result) {
|
|
152
|
-
await this.handleProcedureCompletion(session,
|
|
245
|
+
await this.handleProcedureCompletion(session, sessionId, resultMessage);
|
|
153
246
|
}
|
|
154
|
-
else if (resultMessage.subtype !== "success"
|
|
247
|
+
else if (resultMessage.subtype !== "success" &&
|
|
248
|
+
this.shouldRecoverFromPreviousSubroutine(resultMessage)) {
|
|
155
249
|
// Error result (e.g. error_max_turns from singleTurn subroutines) — try to
|
|
156
250
|
// recover from the last completed subroutine's result so the procedure can still complete.
|
|
157
251
|
const recoveredText = this.procedureAnalyzer?.getLastSubroutineResult(session);
|
|
158
252
|
if (recoveredText) {
|
|
159
|
-
|
|
253
|
+
log.info(`Recovered result from previous subroutine (subtype: ${resultMessage.subtype}), treating as success for procedure completion`);
|
|
160
254
|
// Create a synthetic success result for procedure routing
|
|
161
255
|
const syntheticResult = {
|
|
162
256
|
...resultMessage,
|
|
@@ -164,30 +258,67 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
164
258
|
result: recoveredText,
|
|
165
259
|
is_error: false,
|
|
166
260
|
};
|
|
167
|
-
await this.handleProcedureCompletion(session,
|
|
261
|
+
await this.handleProcedureCompletion(session, sessionId, syntheticResult);
|
|
168
262
|
}
|
|
169
263
|
else {
|
|
170
|
-
|
|
171
|
-
await this.addResultEntry(
|
|
264
|
+
log.warn(`Error result with no recoverable text (subtype: ${resultMessage.subtype}), posting error to Linear`);
|
|
265
|
+
await this.addResultEntry(sessionId, resultMessage);
|
|
172
266
|
}
|
|
173
267
|
}
|
|
268
|
+
else if (resultMessage.subtype !== "success") {
|
|
269
|
+
// Non-recoverable errors (e.g. stop/abort) should not advance procedures.
|
|
270
|
+
await this.addResultEntry(sessionId, resultMessage);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
shouldRecoverFromPreviousSubroutine(resultMessage) {
|
|
274
|
+
if (resultMessage.subtype === "error_max_turns") {
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
const errorText = [
|
|
278
|
+
resultMessage.subtype,
|
|
279
|
+
...("errors" in resultMessage && Array.isArray(resultMessage.errors)
|
|
280
|
+
? resultMessage.errors
|
|
281
|
+
: []),
|
|
282
|
+
"result" in resultMessage && typeof resultMessage.result === "string"
|
|
283
|
+
? resultMessage.result
|
|
284
|
+
: "",
|
|
285
|
+
]
|
|
286
|
+
.join(" ")
|
|
287
|
+
.toLowerCase();
|
|
288
|
+
return (errorText.includes("max turn") ||
|
|
289
|
+
errorText.includes("turn limit") ||
|
|
290
|
+
errorText.includes("turns limit"));
|
|
291
|
+
}
|
|
292
|
+
consumeStopRequest(linearAgentActivitySessionId) {
|
|
293
|
+
if (!this.stopRequestedSessions.has(linearAgentActivitySessionId)) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
this.stopRequestedSessions.delete(linearAgentActivitySessionId);
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
requestSessionStop(linearAgentActivitySessionId) {
|
|
300
|
+
this.stopRequestedSessions.add(linearAgentActivitySessionId);
|
|
174
301
|
}
|
|
175
302
|
/**
|
|
176
303
|
* Handle completion using procedure routing system
|
|
177
304
|
*/
|
|
178
|
-
async handleProcedureCompletion(session,
|
|
305
|
+
async handleProcedureCompletion(session, sessionId, resultMessage) {
|
|
306
|
+
const log = this.sessionLog(sessionId);
|
|
179
307
|
if (!this.procedureAnalyzer) {
|
|
180
308
|
throw new Error("ProcedureAnalyzer not available");
|
|
181
309
|
}
|
|
182
310
|
// Check if error occurred
|
|
183
311
|
if (resultMessage.subtype !== "success") {
|
|
184
|
-
|
|
312
|
+
log.info(`Subroutine completed with error, not triggering next subroutine`);
|
|
185
313
|
return;
|
|
186
314
|
}
|
|
187
|
-
// Get the session ID (
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
315
|
+
// Get the runner session ID (Claude, Gemini, Codex, or Cursor)
|
|
316
|
+
const runnerSessionId = session.claudeSessionId ||
|
|
317
|
+
session.geminiSessionId ||
|
|
318
|
+
session.codexSessionId ||
|
|
319
|
+
session.cursorSessionId;
|
|
320
|
+
if (!runnerSessionId) {
|
|
321
|
+
log.error(`No runner session ID found for procedure session`);
|
|
191
322
|
return;
|
|
192
323
|
}
|
|
193
324
|
// Check if there's a next subroutine
|
|
@@ -196,11 +327,11 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
196
327
|
// More subroutines to run - check if current subroutine requires approval
|
|
197
328
|
const currentSubroutine = this.procedureAnalyzer.getCurrentSubroutine(session);
|
|
198
329
|
if (currentSubroutine?.requiresApproval) {
|
|
199
|
-
|
|
330
|
+
log.info(`Current subroutine "${currentSubroutine.name}" requires approval before proceeding`);
|
|
200
331
|
// Check if SharedApplicationServer is available
|
|
201
332
|
if (!this.sharedApplicationServer) {
|
|
202
|
-
|
|
203
|
-
await this.createErrorActivity(
|
|
333
|
+
log.error(`SharedApplicationServer not available for approval workflow`);
|
|
334
|
+
await this.createErrorActivity(sessionId, "Approval workflow failed: Server not available");
|
|
204
335
|
return;
|
|
205
336
|
}
|
|
206
337
|
// Extract the final result from the completed subroutine
|
|
@@ -209,11 +340,11 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
209
340
|
: "No result available";
|
|
210
341
|
try {
|
|
211
342
|
// Register approval request with server
|
|
212
|
-
const approvalRequest = this.sharedApplicationServer.registerApprovalRequest(
|
|
343
|
+
const approvalRequest = this.sharedApplicationServer.registerApprovalRequest(sessionId);
|
|
213
344
|
// Post approval elicitation to Linear with auth signal URL
|
|
214
345
|
const approvalMessage = `The previous step has completed. Please review the result below and approve to continue:\n\n${subroutineResult}`;
|
|
215
|
-
await this.createApprovalElicitation(
|
|
216
|
-
|
|
346
|
+
await this.createApprovalElicitation(sessionId, approvalMessage, approvalRequest.url);
|
|
347
|
+
log.info(`Waiting for approval at URL: ${approvalRequest.url}`);
|
|
217
348
|
// Wait for approval with timeout (30 minutes)
|
|
218
349
|
const approvalTimeout = 30 * 60 * 1000;
|
|
219
350
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Approval timeout")), approvalTimeout));
|
|
@@ -222,57 +353,57 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
222
353
|
timeoutPromise,
|
|
223
354
|
]);
|
|
224
355
|
if (!approved) {
|
|
225
|
-
|
|
226
|
-
await this.createErrorActivity(
|
|
356
|
+
log.info(`Approval rejected`);
|
|
357
|
+
await this.createErrorActivity(sessionId, `Workflow stopped: User rejected approval.${feedback ? `\n\nFeedback: ${feedback}` : ""}`);
|
|
227
358
|
return; // Stop workflow
|
|
228
359
|
}
|
|
229
|
-
|
|
360
|
+
log.info(`Approval granted, continuing to next subroutine`);
|
|
230
361
|
// Optionally post feedback as a thought
|
|
231
362
|
if (feedback) {
|
|
232
|
-
await this.createThoughtActivity(
|
|
363
|
+
await this.createThoughtActivity(sessionId, `User feedback: ${feedback}`);
|
|
233
364
|
}
|
|
234
365
|
// Continue with advancement (fall through to existing code)
|
|
235
366
|
}
|
|
236
367
|
catch (error) {
|
|
237
368
|
const errorMessage = error.message;
|
|
238
369
|
if (errorMessage === "Approval timeout") {
|
|
239
|
-
|
|
240
|
-
await this.createErrorActivity(
|
|
370
|
+
log.info(`Approval timed out`);
|
|
371
|
+
await this.createErrorActivity(sessionId, "Workflow stopped: Approval request timed out after 30 minutes.");
|
|
241
372
|
}
|
|
242
373
|
else {
|
|
243
|
-
|
|
244
|
-
await this.createErrorActivity(
|
|
374
|
+
log.error(`Approval request failed:`, error);
|
|
375
|
+
await this.createErrorActivity(sessionId, `Workflow stopped: Approval request failed - ${errorMessage}`);
|
|
245
376
|
}
|
|
246
377
|
return; // Stop workflow
|
|
247
378
|
}
|
|
248
379
|
}
|
|
249
380
|
// Check if current subroutine uses validation loop
|
|
250
381
|
if (currentSubroutine?.usesValidationLoop) {
|
|
251
|
-
const handled = await this.handleValidationLoopCompletion(session,
|
|
382
|
+
const handled = await this.handleValidationLoopCompletion(session, sessionId, resultMessage, runnerSessionId, nextSubroutine);
|
|
252
383
|
if (handled) {
|
|
253
384
|
return; // Validation loop took over control flow
|
|
254
385
|
}
|
|
255
386
|
// If not handled (validation passed or max retries), continue with normal advancement
|
|
256
387
|
}
|
|
257
388
|
// Advance procedure state
|
|
258
|
-
|
|
389
|
+
log.info(`Subroutine completed, advancing to next: ${nextSubroutine.name}`);
|
|
259
390
|
const subroutineResult = "result" in resultMessage ? resultMessage.result : undefined;
|
|
260
|
-
this.procedureAnalyzer.advanceToNextSubroutine(session,
|
|
391
|
+
this.procedureAnalyzer.advanceToNextSubroutine(session, runnerSessionId, subroutineResult);
|
|
261
392
|
// Emit event for EdgeWorker to handle subroutine transition
|
|
262
393
|
// This replaces the callback pattern and allows EdgeWorker to subscribe
|
|
263
394
|
this.emit("subroutineComplete", {
|
|
264
|
-
|
|
395
|
+
sessionId,
|
|
265
396
|
session,
|
|
266
397
|
});
|
|
267
398
|
}
|
|
268
399
|
else {
|
|
269
400
|
// Procedure complete - post final result
|
|
270
|
-
|
|
271
|
-
await this.addResultEntry(
|
|
401
|
+
log.info(`All subroutines completed, posting final result to Linear`);
|
|
402
|
+
await this.addResultEntry(sessionId, resultMessage);
|
|
272
403
|
// Handle child session completion
|
|
273
|
-
const isChildSession = this.getParentSessionId?.(
|
|
404
|
+
const isChildSession = this.getParentSessionId?.(sessionId);
|
|
274
405
|
if (isChildSession && this.resumeParentSession) {
|
|
275
|
-
await this.handleChildSessionCompletion(
|
|
406
|
+
await this.handleChildSessionCompletion(sessionId, resultMessage);
|
|
276
407
|
}
|
|
277
408
|
}
|
|
278
409
|
}
|
|
@@ -281,7 +412,8 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
281
412
|
* Returns true if the validation loop took over control flow (needs fixer or retry)
|
|
282
413
|
* Returns false if validation passed or max retries reached (continue with normal advancement)
|
|
283
414
|
*/
|
|
284
|
-
async handleValidationLoopCompletion(session,
|
|
415
|
+
async handleValidationLoopCompletion(session, sessionId, resultMessage, _runnerSessionId, _nextSubroutine) {
|
|
416
|
+
const log = this.sessionLog(sessionId);
|
|
285
417
|
const maxIterations = DEFAULT_VALIDATION_LOOP_CONFIG.maxIterations;
|
|
286
418
|
// Get or initialize validation loop state
|
|
287
419
|
let validationLoop = session.metadata?.procedure?.validationLoop;
|
|
@@ -295,13 +427,13 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
295
427
|
// Check if we're coming back from the fixer
|
|
296
428
|
if (validationLoop.inFixerMode) {
|
|
297
429
|
// Fixer completed, now we need to re-run verifications
|
|
298
|
-
|
|
430
|
+
log.info(`Validation fixer completed for iteration ${validationLoop.iteration}, re-running verifications`);
|
|
299
431
|
// Clear fixer mode flag
|
|
300
432
|
validationLoop.inFixerMode = false;
|
|
301
433
|
this.updateValidationLoopState(session, validationLoop);
|
|
302
434
|
// Emit event to re-run verifications
|
|
303
435
|
this.emit("validationLoopRerun", {
|
|
304
|
-
|
|
436
|
+
sessionId,
|
|
305
437
|
session,
|
|
306
438
|
iteration: validationLoop.iteration,
|
|
307
439
|
});
|
|
@@ -322,27 +454,27 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
322
454
|
reason: validationResult.reason,
|
|
323
455
|
timestamp: Date.now(),
|
|
324
456
|
});
|
|
325
|
-
|
|
457
|
+
log.info(`Validation result for iteration ${newIteration}/${maxIterations}: pass=${validationResult.pass}, reason="${validationResult.reason.substring(0, 100)}..."`);
|
|
326
458
|
// Update state in session
|
|
327
459
|
this.updateValidationLoopState(session, validationLoop);
|
|
328
460
|
// Check if validation passed
|
|
329
461
|
if (validationResult.pass) {
|
|
330
|
-
|
|
462
|
+
log.info(`Validation passed after ${newIteration} iteration(s)`);
|
|
331
463
|
// Clear validation loop state for next subroutine
|
|
332
464
|
this.clearValidationLoopState(session);
|
|
333
465
|
return false; // Continue with normal advancement
|
|
334
466
|
}
|
|
335
467
|
// Check if we've exceeded max retries
|
|
336
468
|
if (newIteration >= maxIterations) {
|
|
337
|
-
|
|
469
|
+
log.info(`Validation failed after ${newIteration} iterations, continuing anyway`);
|
|
338
470
|
// Post a thought about the failures
|
|
339
|
-
await this.createThoughtActivity(
|
|
471
|
+
await this.createThoughtActivity(sessionId, `Validation loop exhausted after ${newIteration} attempts. Last failure: ${validationResult.reason}`);
|
|
340
472
|
// Clear validation loop state for next subroutine
|
|
341
473
|
this.clearValidationLoopState(session);
|
|
342
474
|
return false; // Continue with normal advancement
|
|
343
475
|
}
|
|
344
476
|
// Validation failed and we have retries left - run the fixer
|
|
345
|
-
|
|
477
|
+
log.info(`Validation failed, running fixer (iteration ${newIteration}/${maxIterations})`);
|
|
346
478
|
// Set fixer mode flag
|
|
347
479
|
validationLoop.inFixerMode = true;
|
|
348
480
|
this.updateValidationLoopState(session, validationLoop);
|
|
@@ -359,7 +491,7 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
359
491
|
});
|
|
360
492
|
// Emit event for EdgeWorker to run the fixer
|
|
361
493
|
this.emit("validationLoopIteration", {
|
|
362
|
-
|
|
494
|
+
sessionId,
|
|
363
495
|
session,
|
|
364
496
|
fixerPrompt,
|
|
365
497
|
iteration: newIteration,
|
|
@@ -390,76 +522,78 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
390
522
|
/**
|
|
391
523
|
* Handle child session completion and resume parent
|
|
392
524
|
*/
|
|
393
|
-
async handleChildSessionCompletion(
|
|
525
|
+
async handleChildSessionCompletion(sessionId, resultMessage) {
|
|
526
|
+
const log = this.sessionLog(sessionId);
|
|
394
527
|
if (!this.getParentSessionId || !this.resumeParentSession) {
|
|
395
528
|
return;
|
|
396
529
|
}
|
|
397
|
-
const parentAgentSessionId = this.getParentSessionId(
|
|
530
|
+
const parentAgentSessionId = this.getParentSessionId(sessionId);
|
|
398
531
|
if (!parentAgentSessionId) {
|
|
399
|
-
|
|
532
|
+
log.error(`No parent session ID found for child session`);
|
|
400
533
|
return;
|
|
401
534
|
}
|
|
402
|
-
|
|
535
|
+
log.info(`Child session completed, resuming parent ${parentAgentSessionId}`);
|
|
403
536
|
try {
|
|
404
537
|
const childResult = "result" in resultMessage
|
|
405
538
|
? resultMessage.result
|
|
406
539
|
: "No result available";
|
|
407
|
-
const promptToParent = `Child agent session ${
|
|
408
|
-
await this.resumeParentSession(parentAgentSessionId, promptToParent,
|
|
409
|
-
|
|
540
|
+
const promptToParent = `Child agent session ${sessionId} completed with result:\n\n${childResult}`;
|
|
541
|
+
await this.resumeParentSession(parentAgentSessionId, promptToParent, sessionId);
|
|
542
|
+
log.info(`Successfully resumed parent session ${parentAgentSessionId}`);
|
|
410
543
|
}
|
|
411
544
|
catch (error) {
|
|
412
|
-
|
|
545
|
+
log.error(`Failed to resume parent session:`, error);
|
|
413
546
|
}
|
|
414
547
|
}
|
|
415
548
|
/**
|
|
416
549
|
* Handle streaming Claude messages and route to appropriate methods
|
|
417
550
|
*/
|
|
418
|
-
async handleClaudeMessage(
|
|
551
|
+
async handleClaudeMessage(sessionId, message) {
|
|
552
|
+
const log = this.sessionLog(sessionId);
|
|
419
553
|
try {
|
|
420
554
|
switch (message.type) {
|
|
421
555
|
case "system":
|
|
422
556
|
if (message.subtype === "init") {
|
|
423
|
-
this.updateAgentSessionWithClaudeSessionId(
|
|
557
|
+
this.updateAgentSessionWithClaudeSessionId(sessionId, message);
|
|
424
558
|
// Post model notification
|
|
425
559
|
const systemMessage = message;
|
|
426
560
|
if (systemMessage.model) {
|
|
427
|
-
await this.postModelNotificationThought(
|
|
561
|
+
await this.postModelNotificationThought(sessionId, systemMessage.model);
|
|
428
562
|
}
|
|
429
563
|
}
|
|
430
564
|
else if (message.subtype === "status") {
|
|
431
565
|
// Handle status updates (compacting, etc.)
|
|
432
|
-
await this.handleStatusMessage(
|
|
566
|
+
await this.handleStatusMessage(sessionId, message);
|
|
433
567
|
}
|
|
434
568
|
break;
|
|
435
569
|
case "user": {
|
|
436
|
-
const userEntry = await this.createSessionEntry(
|
|
437
|
-
await this.
|
|
570
|
+
const userEntry = await this.createSessionEntry(sessionId, message);
|
|
571
|
+
await this.syncEntryToActivitySink(userEntry, sessionId);
|
|
438
572
|
break;
|
|
439
573
|
}
|
|
440
574
|
case "assistant": {
|
|
441
|
-
const assistantEntry = await this.createSessionEntry(
|
|
442
|
-
await this.
|
|
575
|
+
const assistantEntry = await this.createSessionEntry(sessionId, message);
|
|
576
|
+
await this.syncEntryToActivitySink(assistantEntry, sessionId);
|
|
443
577
|
break;
|
|
444
578
|
}
|
|
445
579
|
case "result":
|
|
446
|
-
await this.completeSession(
|
|
580
|
+
await this.completeSession(sessionId, message);
|
|
447
581
|
break;
|
|
448
582
|
default:
|
|
449
|
-
|
|
583
|
+
log.warn(`Unknown message type: ${message.type}`);
|
|
450
584
|
}
|
|
451
585
|
}
|
|
452
586
|
catch (error) {
|
|
453
|
-
|
|
587
|
+
log.error(`Error handling message:`, error);
|
|
454
588
|
// Mark session as error state
|
|
455
|
-
await this.updateSessionStatus(
|
|
589
|
+
await this.updateSessionStatus(sessionId, AgentSessionStatus.Error);
|
|
456
590
|
}
|
|
457
591
|
}
|
|
458
592
|
/**
|
|
459
593
|
* Update session status and metadata
|
|
460
594
|
*/
|
|
461
|
-
async updateSessionStatus(
|
|
462
|
-
const session = this.sessions.get(
|
|
595
|
+
async updateSessionStatus(sessionId, status, additionalMetadata) {
|
|
596
|
+
const session = this.sessions.get(sessionId);
|
|
463
597
|
if (!session)
|
|
464
598
|
return;
|
|
465
599
|
session.status = status;
|
|
@@ -467,32 +601,51 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
467
601
|
if (additionalMetadata) {
|
|
468
602
|
session.metadata = { ...session.metadata, ...additionalMetadata };
|
|
469
603
|
}
|
|
470
|
-
this.sessions.set(
|
|
604
|
+
this.sessions.set(sessionId, session);
|
|
471
605
|
}
|
|
472
606
|
/**
|
|
473
607
|
* Add result entry from result message
|
|
474
608
|
*/
|
|
475
|
-
async addResultEntry(
|
|
609
|
+
async addResultEntry(sessionId, resultMessage) {
|
|
476
610
|
// Determine which runner is being used
|
|
477
|
-
const session = this.sessions.get(
|
|
611
|
+
const session = this.sessions.get(sessionId);
|
|
478
612
|
const runner = session?.agentRunner;
|
|
479
|
-
const
|
|
613
|
+
const runnerType = runner?.constructor.name === "GeminiRunner"
|
|
614
|
+
? "gemini"
|
|
615
|
+
: runner?.constructor.name === "CodexRunner"
|
|
616
|
+
? "codex"
|
|
617
|
+
: runner?.constructor.name === "CursorRunner"
|
|
618
|
+
? "cursor"
|
|
619
|
+
: "claude";
|
|
620
|
+
// For error results, content may be in errors[] rather than result
|
|
621
|
+
const content = "result" in resultMessage && typeof resultMessage.result === "string"
|
|
622
|
+
? resultMessage.result
|
|
623
|
+
: resultMessage.is_error &&
|
|
624
|
+
"errors" in resultMessage &&
|
|
625
|
+
Array.isArray(resultMessage.errors) &&
|
|
626
|
+
resultMessage.errors.length > 0
|
|
627
|
+
? resultMessage.errors.join("\n")
|
|
628
|
+
: "";
|
|
480
629
|
const resultEntry = {
|
|
481
630
|
// Set the appropriate session ID based on runner type
|
|
482
|
-
...(
|
|
631
|
+
...(runnerType === "gemini"
|
|
483
632
|
? { geminiSessionId: resultMessage.session_id }
|
|
484
|
-
:
|
|
633
|
+
: runnerType === "codex"
|
|
634
|
+
? { codexSessionId: resultMessage.session_id }
|
|
635
|
+
: runnerType === "cursor"
|
|
636
|
+
? { cursorSessionId: resultMessage.session_id }
|
|
637
|
+
: { claudeSessionId: resultMessage.session_id }),
|
|
485
638
|
type: "result",
|
|
486
|
-
content
|
|
639
|
+
content,
|
|
487
640
|
metadata: {
|
|
488
641
|
timestamp: Date.now(),
|
|
489
642
|
durationMs: resultMessage.duration_ms,
|
|
490
643
|
isError: resultMessage.is_error,
|
|
491
644
|
},
|
|
492
645
|
};
|
|
493
|
-
// DON'T store locally -
|
|
646
|
+
// DON'T store locally - syncEntryToActivitySink will do it
|
|
494
647
|
// Sync to Linear
|
|
495
|
-
await this.
|
|
648
|
+
await this.syncEntryToActivitySink(resultEntry, sessionId);
|
|
496
649
|
}
|
|
497
650
|
/**
|
|
498
651
|
* Extract content from Claude message
|
|
@@ -586,31 +739,32 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
586
739
|
};
|
|
587
740
|
}
|
|
588
741
|
/**
|
|
589
|
-
* Sync
|
|
742
|
+
* Sync session entry to external tracker (create AgentActivity)
|
|
590
743
|
*/
|
|
591
|
-
async
|
|
744
|
+
async syncEntryToActivitySink(entry, sessionId) {
|
|
745
|
+
const log = this.sessionLog(sessionId);
|
|
592
746
|
try {
|
|
593
|
-
const session = this.sessions.get(
|
|
747
|
+
const session = this.sessions.get(sessionId);
|
|
594
748
|
if (!session) {
|
|
595
|
-
|
|
749
|
+
log.warn(`No session found`);
|
|
596
750
|
return;
|
|
597
751
|
}
|
|
598
752
|
// Store entry locally first
|
|
599
|
-
const entries = this.entries.get(
|
|
753
|
+
const entries = this.entries.get(sessionId) || [];
|
|
600
754
|
entries.push(entry);
|
|
601
|
-
this.entries.set(
|
|
755
|
+
this.entries.set(sessionId, entries);
|
|
602
756
|
// Build activity content based on entry type
|
|
603
757
|
let content;
|
|
604
758
|
let ephemeral = false;
|
|
605
759
|
switch (entry.type) {
|
|
606
760
|
case "user": {
|
|
607
|
-
const activeTaskId = this.activeTasksBySession.get(
|
|
761
|
+
const activeTaskId = this.activeTasksBySession.get(sessionId);
|
|
608
762
|
if (activeTaskId && activeTaskId === entry.metadata?.toolUseId) {
|
|
609
763
|
content = {
|
|
610
764
|
type: "thought",
|
|
611
765
|
body: `✅ Task Completed\n\n\n\n${entry.content}\n\n---\n\n`,
|
|
612
766
|
};
|
|
613
|
-
this.activeTasksBySession.delete(
|
|
767
|
+
this.activeTasksBySession.delete(sessionId);
|
|
614
768
|
}
|
|
615
769
|
else if (entry.metadata?.toolUseId) {
|
|
616
770
|
// This is a tool result - create an action activity with the result
|
|
@@ -641,7 +795,7 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
641
795
|
if (baseToolName === "TaskUpdate" || baseToolName === "TaskGet") {
|
|
642
796
|
const formatter = session.agentRunner?.getFormatter();
|
|
643
797
|
if (!formatter) {
|
|
644
|
-
|
|
798
|
+
log.warn(`No formatter available for session ${sessionId}`);
|
|
645
799
|
return;
|
|
646
800
|
}
|
|
647
801
|
// Try to enrich toolInput with subject from cache or result
|
|
@@ -706,7 +860,7 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
706
860
|
// Get formatter from runner
|
|
707
861
|
const formatter = session.agentRunner?.getFormatter();
|
|
708
862
|
if (!formatter) {
|
|
709
|
-
|
|
863
|
+
log.warn(`No formatter available`);
|
|
710
864
|
return;
|
|
711
865
|
}
|
|
712
866
|
// Format parameter and result using runner's formatter
|
|
@@ -739,7 +893,7 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
739
893
|
// Check if this is a subtask with arrow prefix
|
|
740
894
|
let storedName = toolName;
|
|
741
895
|
if (entry.metadata?.parentToolUseId) {
|
|
742
|
-
const activeTaskId = this.activeTasksBySession.get(
|
|
896
|
+
const activeTaskId = this.activeTasksBySession.get(sessionId);
|
|
743
897
|
if (activeTaskId === entry.metadata?.parentToolUseId) {
|
|
744
898
|
storedName = `↪ ${toolName}`;
|
|
745
899
|
}
|
|
@@ -758,7 +912,7 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
758
912
|
// Get formatter from runner
|
|
759
913
|
const formatter = session.agentRunner?.getFormatter();
|
|
760
914
|
if (!formatter) {
|
|
761
|
-
|
|
915
|
+
log.warn(`No formatter available`);
|
|
762
916
|
return;
|
|
763
917
|
}
|
|
764
918
|
const formattedTodos = formatter.formatTodoWriteParameter(entry.content);
|
|
@@ -773,7 +927,7 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
773
927
|
// Get formatter from runner
|
|
774
928
|
const formatter = session.agentRunner?.getFormatter();
|
|
775
929
|
if (!formatter) {
|
|
776
|
-
|
|
930
|
+
log.warn(`No formatter available for session ${sessionId}`);
|
|
777
931
|
return;
|
|
778
932
|
}
|
|
779
933
|
// Special handling for Task tools - format as thought instead of action
|
|
@@ -801,7 +955,7 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
801
955
|
// Get formatter from runner
|
|
802
956
|
const formatter = session.agentRunner?.getFormatter();
|
|
803
957
|
if (!formatter) {
|
|
804
|
-
|
|
958
|
+
log.warn(`No formatter available for session ${sessionId}`);
|
|
805
959
|
return;
|
|
806
960
|
}
|
|
807
961
|
// Special handling for ToolSearch - format as thought instead of action
|
|
@@ -818,7 +972,7 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
818
972
|
// Get formatter from runner
|
|
819
973
|
const formatter = session.agentRunner?.getFormatter();
|
|
820
974
|
if (!formatter) {
|
|
821
|
-
|
|
975
|
+
log.warn(`No formatter available`);
|
|
822
976
|
return;
|
|
823
977
|
}
|
|
824
978
|
// Special handling for Task tool - add start marker and track active task
|
|
@@ -827,7 +981,7 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
827
981
|
const displayName = toolName;
|
|
828
982
|
// Track this as the active Task for this session
|
|
829
983
|
if (entry.metadata?.toolUseId) {
|
|
830
|
-
this.activeTasksBySession.set(
|
|
984
|
+
this.activeTasksBySession.set(sessionId, entry.metadata.toolUseId);
|
|
831
985
|
}
|
|
832
986
|
content = {
|
|
833
987
|
type: "action",
|
|
@@ -842,14 +996,14 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
842
996
|
// Get formatter from runner
|
|
843
997
|
const formatter = session.agentRunner?.getFormatter();
|
|
844
998
|
if (!formatter) {
|
|
845
|
-
|
|
999
|
+
log.warn(`No formatter available`);
|
|
846
1000
|
return;
|
|
847
1001
|
}
|
|
848
1002
|
// Other tools - check if they're within an active Task
|
|
849
1003
|
const toolInput = entry.metadata.toolInput || entry.content;
|
|
850
1004
|
let displayName = toolName;
|
|
851
1005
|
if (entry.metadata?.parentToolUseId) {
|
|
852
|
-
const activeTaskId = this.activeTasksBySession.get(
|
|
1006
|
+
const activeTaskId = this.activeTasksBySession.get(sessionId);
|
|
853
1007
|
if (activeTaskId === entry.metadata?.parentToolUseId) {
|
|
854
1008
|
displayName = `↪ ${toolName}`;
|
|
855
1009
|
}
|
|
@@ -918,40 +1072,45 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
918
1072
|
if (currentSubroutine?.suppressThoughtPosting) {
|
|
919
1073
|
// Only suppress thoughts and actions, not responses or results
|
|
920
1074
|
if (content.type === "thought" || content.type === "action") {
|
|
921
|
-
|
|
922
|
-
return; // Don't post to
|
|
1075
|
+
log.debug(`Suppressing ${content.type} posting for subroutine "${currentSubroutine.name}"`);
|
|
1076
|
+
return; // Don't post to tracker
|
|
923
1077
|
}
|
|
924
1078
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
};
|
|
930
|
-
const result = await this.issueTracker.createAgentActivity(activityInput);
|
|
931
|
-
if (result.success && result.agentActivity) {
|
|
932
|
-
const agentActivity = await result.agentActivity;
|
|
933
|
-
entry.linearAgentActivityId = agentActivity.id;
|
|
934
|
-
console.log(`[AgentSessionManager] Created ${content.type} activity ${entry.linearAgentActivityId}`);
|
|
1079
|
+
// Ensure we have an external session ID for activity posting
|
|
1080
|
+
if (!session.externalSessionId) {
|
|
1081
|
+
log.debug(`Skipping activity sync - no external session ID (platform: ${session.issueContext?.trackerId || "unknown"})`);
|
|
1082
|
+
return;
|
|
935
1083
|
}
|
|
936
|
-
|
|
937
|
-
|
|
1084
|
+
const options = {};
|
|
1085
|
+
if (ephemeral) {
|
|
1086
|
+
options.ephemeral = true;
|
|
1087
|
+
}
|
|
1088
|
+
const result = await this.activitySink.postActivity(session.externalSessionId, content, options);
|
|
1089
|
+
if (result.activityId) {
|
|
1090
|
+
entry.linearAgentActivityId = result.activityId;
|
|
1091
|
+
if (entry.type === "result") {
|
|
1092
|
+
log.info(`Result message emitted to Linear (activity ${entry.linearAgentActivityId})`);
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
log.debug(`Created ${content.type} activity ${entry.linearAgentActivityId}`);
|
|
1096
|
+
}
|
|
938
1097
|
}
|
|
939
1098
|
}
|
|
940
1099
|
catch (error) {
|
|
941
|
-
|
|
1100
|
+
log.error(`Failed to sync entry to activity sink:`, error);
|
|
942
1101
|
}
|
|
943
1102
|
}
|
|
944
1103
|
/**
|
|
945
1104
|
* Get session by ID
|
|
946
1105
|
*/
|
|
947
|
-
getSession(
|
|
948
|
-
return this.sessions.get(
|
|
1106
|
+
getSession(sessionId) {
|
|
1107
|
+
return this.sessions.get(sessionId);
|
|
949
1108
|
}
|
|
950
1109
|
/**
|
|
951
1110
|
* Get session entries by session ID
|
|
952
1111
|
*/
|
|
953
|
-
getSessionEntries(
|
|
954
|
-
return this.entries.get(
|
|
1112
|
+
getSessionEntries(sessionId) {
|
|
1113
|
+
return this.entries.get(sessionId) || [];
|
|
955
1114
|
}
|
|
956
1115
|
/**
|
|
957
1116
|
* Get all active sessions
|
|
@@ -962,15 +1121,16 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
962
1121
|
/**
|
|
963
1122
|
* Add or update agent runner for a session
|
|
964
1123
|
*/
|
|
965
|
-
addAgentRunner(
|
|
966
|
-
const
|
|
1124
|
+
addAgentRunner(sessionId, agentRunner) {
|
|
1125
|
+
const log = this.sessionLog(sessionId);
|
|
1126
|
+
const session = this.sessions.get(sessionId);
|
|
967
1127
|
if (!session) {
|
|
968
|
-
|
|
1128
|
+
log.warn(`No session found`);
|
|
969
1129
|
return;
|
|
970
1130
|
}
|
|
971
1131
|
session.agentRunner = agentRunner;
|
|
972
1132
|
session.updatedAt = Date.now();
|
|
973
|
-
|
|
1133
|
+
log.debug(`Added agent runner`);
|
|
974
1134
|
}
|
|
975
1135
|
/**
|
|
976
1136
|
* Get all agent runners
|
|
@@ -980,12 +1140,18 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
980
1140
|
.map((session) => session.agentRunner)
|
|
981
1141
|
.filter((runner) => runner !== undefined);
|
|
982
1142
|
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Resolve the issue ID from a session, checking issueContext first then deprecated issueId.
|
|
1145
|
+
*/
|
|
1146
|
+
getSessionIssueId(session) {
|
|
1147
|
+
return session.issueContext?.issueId ?? session.issueId;
|
|
1148
|
+
}
|
|
983
1149
|
/**
|
|
984
1150
|
* Get all agent runners for a specific issue
|
|
985
1151
|
*/
|
|
986
1152
|
getAgentRunnersForIssue(issueId) {
|
|
987
1153
|
return Array.from(this.sessions.values())
|
|
988
|
-
.filter((session) => session
|
|
1154
|
+
.filter((session) => this.getSessionIssueId(session) === issueId)
|
|
989
1155
|
.map((session) => session.agentRunner)
|
|
990
1156
|
.filter((runner) => runner !== undefined);
|
|
991
1157
|
}
|
|
@@ -993,15 +1159,23 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
993
1159
|
* Get sessions by issue ID
|
|
994
1160
|
*/
|
|
995
1161
|
getSessionsByIssueId(issueId) {
|
|
996
|
-
return Array.from(this.sessions.values()).filter((session) => session
|
|
1162
|
+
return Array.from(this.sessions.values()).filter((session) => this.getSessionIssueId(session) === issueId);
|
|
997
1163
|
}
|
|
998
1164
|
/**
|
|
999
1165
|
* Get active sessions by issue ID
|
|
1000
1166
|
*/
|
|
1001
1167
|
getActiveSessionsByIssueId(issueId) {
|
|
1002
|
-
return Array.from(this.sessions.values()).filter((session) => session
|
|
1168
|
+
return Array.from(this.sessions.values()).filter((session) => this.getSessionIssueId(session) === issueId &&
|
|
1003
1169
|
session.status === AgentSessionStatus.Active);
|
|
1004
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Get active sessions where the issue's branch name matches the given branch.
|
|
1173
|
+
* Useful for detecting when multiple sessions share the same worktree.
|
|
1174
|
+
*/
|
|
1175
|
+
getActiveSessionsByBranchName(branchName) {
|
|
1176
|
+
return Array.from(this.sessions.values()).filter((session) => session.status === AgentSessionStatus.Active &&
|
|
1177
|
+
session.issue?.branchName === branchName);
|
|
1178
|
+
}
|
|
1005
1179
|
/**
|
|
1006
1180
|
* Get all sessions
|
|
1007
1181
|
*/
|
|
@@ -1011,193 +1185,97 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
1011
1185
|
/**
|
|
1012
1186
|
* Get agent runner for a specific session
|
|
1013
1187
|
*/
|
|
1014
|
-
getAgentRunner(
|
|
1015
|
-
const session = this.sessions.get(
|
|
1188
|
+
getAgentRunner(sessionId) {
|
|
1189
|
+
const session = this.sessions.get(sessionId);
|
|
1016
1190
|
return session?.agentRunner;
|
|
1017
1191
|
}
|
|
1018
1192
|
/**
|
|
1019
1193
|
* Check if an agent runner exists for a session
|
|
1020
1194
|
*/
|
|
1021
|
-
hasAgentRunner(
|
|
1022
|
-
const session = this.sessions.get(
|
|
1195
|
+
hasAgentRunner(sessionId) {
|
|
1196
|
+
const session = this.sessions.get(sessionId);
|
|
1023
1197
|
return session?.agentRunner !== undefined;
|
|
1024
1198
|
}
|
|
1025
1199
|
/**
|
|
1026
|
-
*
|
|
1200
|
+
* Post an activity to the activity sink for a session.
|
|
1201
|
+
* Consolidates session lookup, externalSessionId guard, try/catch, and logging.
|
|
1202
|
+
*
|
|
1203
|
+
* @returns The activity ID when resolved, `null` otherwise.
|
|
1027
1204
|
*/
|
|
1028
|
-
async
|
|
1205
|
+
async postActivity(sessionId, input, label) {
|
|
1206
|
+
const log = this.sessionLog(sessionId);
|
|
1029
1207
|
const session = this.sessions.get(sessionId);
|
|
1030
|
-
if (!session || !session.
|
|
1031
|
-
|
|
1032
|
-
return;
|
|
1208
|
+
if (!session || !session.externalSessionId) {
|
|
1209
|
+
log.debug(`Skipping ${label} - no external session ID (platform: ${session?.issueContext?.trackerId || "unknown"})`);
|
|
1210
|
+
return null;
|
|
1033
1211
|
}
|
|
1034
1212
|
try {
|
|
1035
|
-
const
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
type: "thought",
|
|
1039
|
-
body,
|
|
1040
|
-
},
|
|
1041
|
-
});
|
|
1042
|
-
if (result.success) {
|
|
1043
|
-
console.log(`[AgentSessionManager] Created thought activity for session ${sessionId}`);
|
|
1213
|
+
const options = {};
|
|
1214
|
+
if (input.ephemeral !== undefined) {
|
|
1215
|
+
options.ephemeral = input.ephemeral;
|
|
1044
1216
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1217
|
+
if (input.signal) {
|
|
1218
|
+
options.signal = input.signal;
|
|
1219
|
+
}
|
|
1220
|
+
if (input.signalMetadata) {
|
|
1221
|
+
options.signalMetadata = input.signalMetadata;
|
|
1222
|
+
}
|
|
1223
|
+
const result = await this.activitySink.postActivity(session.externalSessionId, input.content, options);
|
|
1224
|
+
if (result.activityId) {
|
|
1225
|
+
log.debug(`Created ${label} activity ${result.activityId}`);
|
|
1226
|
+
return result.activityId;
|
|
1047
1227
|
}
|
|
1228
|
+
log.debug(`Created ${label}`);
|
|
1229
|
+
return null;
|
|
1048
1230
|
}
|
|
1049
1231
|
catch (error) {
|
|
1050
|
-
|
|
1232
|
+
log.error(`Error creating ${label}:`, error);
|
|
1233
|
+
return null;
|
|
1051
1234
|
}
|
|
1052
1235
|
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Create a thought activity
|
|
1238
|
+
*/
|
|
1239
|
+
async createThoughtActivity(sessionId, body) {
|
|
1240
|
+
await this.postActivity(sessionId, { content: { type: "thought", body } }, "thought");
|
|
1241
|
+
}
|
|
1053
1242
|
/**
|
|
1054
1243
|
* Create an action activity
|
|
1055
1244
|
*/
|
|
1056
1245
|
async createActionActivity(sessionId, action, parameter, result) {
|
|
1057
|
-
const
|
|
1058
|
-
if (
|
|
1059
|
-
|
|
1060
|
-
return;
|
|
1061
|
-
}
|
|
1062
|
-
try {
|
|
1063
|
-
const content = {
|
|
1064
|
-
type: "action",
|
|
1065
|
-
action,
|
|
1066
|
-
parameter,
|
|
1067
|
-
};
|
|
1068
|
-
if (result !== undefined) {
|
|
1069
|
-
content.result = result;
|
|
1070
|
-
}
|
|
1071
|
-
const response = await this.issueTracker.createAgentActivity({
|
|
1072
|
-
agentSessionId: session.linearAgentActivitySessionId,
|
|
1073
|
-
content,
|
|
1074
|
-
});
|
|
1075
|
-
if (response.success) {
|
|
1076
|
-
console.log(`[AgentSessionManager] Created action activity for session ${sessionId}`);
|
|
1077
|
-
}
|
|
1078
|
-
else {
|
|
1079
|
-
console.error(`[AgentSessionManager] Failed to create action activity:`, response);
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
catch (error) {
|
|
1083
|
-
console.error(`[AgentSessionManager] Error creating action activity:`, error);
|
|
1246
|
+
const content = { type: "action", action, parameter };
|
|
1247
|
+
if (result !== undefined) {
|
|
1248
|
+
content.result = result;
|
|
1084
1249
|
}
|
|
1250
|
+
await this.postActivity(sessionId, { content }, "action");
|
|
1085
1251
|
}
|
|
1086
1252
|
/**
|
|
1087
1253
|
* Create a response activity
|
|
1088
1254
|
*/
|
|
1089
1255
|
async createResponseActivity(sessionId, body) {
|
|
1090
|
-
|
|
1091
|
-
if (!session || !session.linearAgentActivitySessionId) {
|
|
1092
|
-
console.warn(`[AgentSessionManager] No Linear session ID for session ${sessionId}`);
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
try {
|
|
1096
|
-
const result = await this.issueTracker.createAgentActivity({
|
|
1097
|
-
agentSessionId: session.linearAgentActivitySessionId,
|
|
1098
|
-
content: {
|
|
1099
|
-
type: "response",
|
|
1100
|
-
body,
|
|
1101
|
-
},
|
|
1102
|
-
});
|
|
1103
|
-
if (result.success) {
|
|
1104
|
-
console.log(`[AgentSessionManager] Created response activity for session ${sessionId}`);
|
|
1105
|
-
}
|
|
1106
|
-
else {
|
|
1107
|
-
console.error(`[AgentSessionManager] Failed to create response activity:`, result);
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
catch (error) {
|
|
1111
|
-
console.error(`[AgentSessionManager] Error creating response activity:`, error);
|
|
1112
|
-
}
|
|
1256
|
+
await this.postActivity(sessionId, { content: { type: "response", body } }, "response");
|
|
1113
1257
|
}
|
|
1114
1258
|
/**
|
|
1115
1259
|
* Create an error activity
|
|
1116
1260
|
*/
|
|
1117
1261
|
async createErrorActivity(sessionId, body) {
|
|
1118
|
-
|
|
1119
|
-
if (!session || !session.linearAgentActivitySessionId) {
|
|
1120
|
-
console.warn(`[AgentSessionManager] No Linear session ID for session ${sessionId}`);
|
|
1121
|
-
return;
|
|
1122
|
-
}
|
|
1123
|
-
try {
|
|
1124
|
-
const result = await this.issueTracker.createAgentActivity({
|
|
1125
|
-
agentSessionId: session.linearAgentActivitySessionId,
|
|
1126
|
-
content: {
|
|
1127
|
-
type: "error",
|
|
1128
|
-
body,
|
|
1129
|
-
},
|
|
1130
|
-
});
|
|
1131
|
-
if (result.success) {
|
|
1132
|
-
console.log(`[AgentSessionManager] Created error activity for session ${sessionId}`);
|
|
1133
|
-
}
|
|
1134
|
-
else {
|
|
1135
|
-
console.error(`[AgentSessionManager] Failed to create error activity:`, result);
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
catch (error) {
|
|
1139
|
-
console.error(`[AgentSessionManager] Error creating error activity:`, error);
|
|
1140
|
-
}
|
|
1262
|
+
await this.postActivity(sessionId, { content: { type: "error", body } }, "error");
|
|
1141
1263
|
}
|
|
1142
1264
|
/**
|
|
1143
1265
|
* Create an elicitation activity
|
|
1144
1266
|
*/
|
|
1145
1267
|
async createElicitationActivity(sessionId, body) {
|
|
1146
|
-
|
|
1147
|
-
if (!session || !session.linearAgentActivitySessionId) {
|
|
1148
|
-
console.warn(`[AgentSessionManager] No Linear session ID for session ${sessionId}`);
|
|
1149
|
-
return;
|
|
1150
|
-
}
|
|
1151
|
-
try {
|
|
1152
|
-
const result = await this.issueTracker.createAgentActivity({
|
|
1153
|
-
agentSessionId: session.linearAgentActivitySessionId,
|
|
1154
|
-
content: {
|
|
1155
|
-
type: "elicitation",
|
|
1156
|
-
body,
|
|
1157
|
-
},
|
|
1158
|
-
});
|
|
1159
|
-
if (result.success) {
|
|
1160
|
-
console.log(`[AgentSessionManager] Created elicitation activity for session ${sessionId}`);
|
|
1161
|
-
}
|
|
1162
|
-
else {
|
|
1163
|
-
console.error(`[AgentSessionManager] Failed to create elicitation activity:`, result);
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
catch (error) {
|
|
1167
|
-
console.error(`[AgentSessionManager] Error creating elicitation activity:`, error);
|
|
1168
|
-
}
|
|
1268
|
+
await this.postActivity(sessionId, { content: { type: "elicitation", body } }, "elicitation");
|
|
1169
1269
|
}
|
|
1170
1270
|
/**
|
|
1171
1271
|
* Create an approval elicitation activity with auth signal
|
|
1172
1272
|
*/
|
|
1173
1273
|
async createApprovalElicitation(sessionId, body, approvalUrl) {
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
}
|
|
1179
|
-
try {
|
|
1180
|
-
const result = await this.issueTracker.createAgentActivity({
|
|
1181
|
-
agentSessionId: session.linearAgentActivitySessionId,
|
|
1182
|
-
content: {
|
|
1183
|
-
type: "elicitation",
|
|
1184
|
-
body,
|
|
1185
|
-
},
|
|
1186
|
-
signal: AgentActivitySignal.Auth,
|
|
1187
|
-
signalMetadata: {
|
|
1188
|
-
url: approvalUrl,
|
|
1189
|
-
},
|
|
1190
|
-
});
|
|
1191
|
-
if (result.success) {
|
|
1192
|
-
console.log(`[AgentSessionManager] Created approval elicitation for session ${sessionId} with URL: ${approvalUrl}`);
|
|
1193
|
-
}
|
|
1194
|
-
else {
|
|
1195
|
-
console.error(`[AgentSessionManager] Failed to create approval elicitation:`, result);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
catch (error) {
|
|
1199
|
-
console.error(`[AgentSessionManager] Error creating approval elicitation:`, error);
|
|
1200
|
-
}
|
|
1274
|
+
await this.postActivity(sessionId, {
|
|
1275
|
+
content: { type: "elicitation", body },
|
|
1276
|
+
signal: "auth",
|
|
1277
|
+
signalMetadata: { url: approvalUrl },
|
|
1278
|
+
}, "approval elicitation");
|
|
1201
1279
|
}
|
|
1202
1280
|
/**
|
|
1203
1281
|
* Clear completed sessions older than specified time
|
|
@@ -1207,9 +1285,10 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
1207
1285
|
for (const [sessionId, session] of this.sessions.entries()) {
|
|
1208
1286
|
if ((session.status === "complete" || session.status === "error") &&
|
|
1209
1287
|
session.updatedAt < cutoff) {
|
|
1288
|
+
const log = this.sessionLog(sessionId);
|
|
1210
1289
|
this.sessions.delete(sessionId);
|
|
1211
1290
|
this.entries.delete(sessionId);
|
|
1212
|
-
|
|
1291
|
+
log.debug(`Cleaned up session`);
|
|
1213
1292
|
}
|
|
1214
1293
|
}
|
|
1215
1294
|
}
|
|
@@ -1254,135 +1333,65 @@ export class AgentSessionManager extends EventEmitter {
|
|
|
1254
1333
|
}));
|
|
1255
1334
|
this.entries.set(sessionId, sessionEntries);
|
|
1256
1335
|
}
|
|
1257
|
-
|
|
1336
|
+
this.logger.debug(`Restored ${this.sessions.size} sessions, ${Object.keys(serializedEntries).length} entry collections`);
|
|
1258
1337
|
}
|
|
1259
1338
|
/**
|
|
1260
1339
|
* Post a thought about the model being used
|
|
1261
1340
|
*/
|
|
1262
|
-
async postModelNotificationThought(
|
|
1263
|
-
|
|
1264
|
-
const result = await this.issueTracker.createAgentActivity({
|
|
1265
|
-
agentSessionId: linearAgentActivitySessionId,
|
|
1266
|
-
content: {
|
|
1267
|
-
type: "thought",
|
|
1268
|
-
body: `Using model: ${model}`,
|
|
1269
|
-
},
|
|
1270
|
-
});
|
|
1271
|
-
if (result.success) {
|
|
1272
|
-
console.log(`[AgentSessionManager] Posted model notification for session ${linearAgentActivitySessionId} (model: ${model})`);
|
|
1273
|
-
}
|
|
1274
|
-
else {
|
|
1275
|
-
console.error(`[AgentSessionManager] Failed to post model notification:`, result);
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
catch (error) {
|
|
1279
|
-
console.error(`[AgentSessionManager] Error posting model notification:`, error);
|
|
1280
|
-
}
|
|
1341
|
+
async postModelNotificationThought(sessionId, model) {
|
|
1342
|
+
await this.postActivity(sessionId, { content: { type: "thought", body: `Using model: ${model}` } }, "model notification");
|
|
1281
1343
|
}
|
|
1282
1344
|
/**
|
|
1283
1345
|
* Post an ephemeral "Analyzing your request..." thought and return the activity ID
|
|
1284
1346
|
*/
|
|
1285
|
-
async postAnalyzingThought(
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
type: "thought",
|
|
1291
|
-
body: "Analyzing your request…",
|
|
1292
|
-
},
|
|
1293
|
-
ephemeral: true,
|
|
1294
|
-
});
|
|
1295
|
-
if (result.success && result.agentActivity) {
|
|
1296
|
-
const activity = await result.agentActivity;
|
|
1297
|
-
console.log(`[AgentSessionManager] Posted analyzing thought for session ${linearAgentActivitySessionId}`);
|
|
1298
|
-
return activity.id;
|
|
1299
|
-
}
|
|
1300
|
-
else {
|
|
1301
|
-
console.error(`[AgentSessionManager] Failed to post analyzing thought:`, result);
|
|
1302
|
-
return null;
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
catch (error) {
|
|
1306
|
-
console.error(`[AgentSessionManager] Error posting analyzing thought:`, error);
|
|
1307
|
-
return null;
|
|
1308
|
-
}
|
|
1347
|
+
async postAnalyzingThought(sessionId) {
|
|
1348
|
+
return this.postActivity(sessionId, {
|
|
1349
|
+
content: { type: "thought", body: "Analyzing your request…" },
|
|
1350
|
+
ephemeral: true,
|
|
1351
|
+
}, "analyzing thought");
|
|
1309
1352
|
}
|
|
1310
1353
|
/**
|
|
1311
1354
|
* Post the procedure selection result as a non-ephemeral thought
|
|
1312
1355
|
*/
|
|
1313
|
-
async postProcedureSelectionThought(
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
ephemeral: false,
|
|
1322
|
-
});
|
|
1323
|
-
if (result.success) {
|
|
1324
|
-
console.log(`[AgentSessionManager] Posted procedure selection for session ${linearAgentActivitySessionId}: ${procedureName}`);
|
|
1325
|
-
}
|
|
1326
|
-
else {
|
|
1327
|
-
console.error(`[AgentSessionManager] Failed to post procedure selection:`, result);
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
catch (error) {
|
|
1331
|
-
console.error(`[AgentSessionManager] Error posting procedure selection:`, error);
|
|
1332
|
-
}
|
|
1356
|
+
async postProcedureSelectionThought(sessionId, procedureName, classification) {
|
|
1357
|
+
await this.postActivity(sessionId, {
|
|
1358
|
+
content: {
|
|
1359
|
+
type: "thought",
|
|
1360
|
+
body: `Selected procedure: **${procedureName}** (classified as: ${classification})`,
|
|
1361
|
+
},
|
|
1362
|
+
ephemeral: false,
|
|
1363
|
+
}, "procedure selection");
|
|
1333
1364
|
}
|
|
1334
1365
|
/**
|
|
1335
1366
|
* Handle status messages (compacting, etc.)
|
|
1336
1367
|
*/
|
|
1337
|
-
async handleStatusMessage(
|
|
1338
|
-
const session = this.sessions.get(
|
|
1339
|
-
if (!session || !session.
|
|
1340
|
-
|
|
1368
|
+
async handleStatusMessage(sessionId, message) {
|
|
1369
|
+
const session = this.sessions.get(sessionId);
|
|
1370
|
+
if (!session || !session.externalSessionId) {
|
|
1371
|
+
const log = this.sessionLog(sessionId);
|
|
1372
|
+
log.debug(`Skipping status message - no external session ID (platform: ${session?.issueContext?.trackerId || "unknown"})`);
|
|
1341
1373
|
return;
|
|
1342
1374
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
});
|
|
1354
|
-
if (result.success && result.agentActivity) {
|
|
1355
|
-
const activity = await result.agentActivity;
|
|
1356
|
-
// Store the activity ID so we can replace it later
|
|
1357
|
-
this.activeStatusActivitiesBySession.set(linearAgentActivitySessionId, activity.id);
|
|
1358
|
-
console.log(`[AgentSessionManager] Posted ephemeral compacting status for session ${linearAgentActivitySessionId}`);
|
|
1359
|
-
}
|
|
1360
|
-
else {
|
|
1361
|
-
console.error(`[AgentSessionManager] Failed to post compacting status:`, result);
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
else if (message.status === null) {
|
|
1365
|
-
// Clear the status - post a non-ephemeral thought to replace the ephemeral one
|
|
1366
|
-
const result = await this.issueTracker.createAgentActivity({
|
|
1367
|
-
agentSessionId: session.linearAgentActivitySessionId,
|
|
1368
|
-
content: {
|
|
1369
|
-
type: "thought",
|
|
1370
|
-
body: "Conversation history compacted",
|
|
1371
|
-
},
|
|
1372
|
-
ephemeral: false,
|
|
1373
|
-
});
|
|
1374
|
-
if (result.success) {
|
|
1375
|
-
// Clean up the stored activity ID
|
|
1376
|
-
this.activeStatusActivitiesBySession.delete(linearAgentActivitySessionId);
|
|
1377
|
-
console.log(`[AgentSessionManager] Posted non-ephemeral status clear for session ${linearAgentActivitySessionId}`);
|
|
1378
|
-
}
|
|
1379
|
-
else {
|
|
1380
|
-
console.error(`[AgentSessionManager] Failed to post status clear:`, result);
|
|
1381
|
-
}
|
|
1375
|
+
if (message.status === "compacting") {
|
|
1376
|
+
const activityId = await this.postActivity(sessionId, {
|
|
1377
|
+
content: {
|
|
1378
|
+
type: "thought",
|
|
1379
|
+
body: "Compacting conversation history…",
|
|
1380
|
+
},
|
|
1381
|
+
ephemeral: true,
|
|
1382
|
+
}, "compacting status");
|
|
1383
|
+
if (activityId) {
|
|
1384
|
+
this.activeStatusActivitiesBySession.set(sessionId, activityId);
|
|
1382
1385
|
}
|
|
1383
1386
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1387
|
+
else if (message.status === null) {
|
|
1388
|
+
// Clear the status - post a non-ephemeral thought to replace the ephemeral one
|
|
1389
|
+
await this.postActivity(sessionId, {
|
|
1390
|
+
content: { type: "thought", body: "Conversation history compacted" },
|
|
1391
|
+
ephemeral: false,
|
|
1392
|
+
}, "status clear");
|
|
1393
|
+
// Clean up the stored activity ID regardless — stale IDs do no harm
|
|
1394
|
+
this.activeStatusActivitiesBySession.delete(sessionId);
|
|
1386
1395
|
}
|
|
1387
1396
|
}
|
|
1388
1397
|
}
|