@northflare/runner 0.0.11 → 0.0.13
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/utils/config.d.ts +1 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +13 -2
- package/dist/utils/config.js.map +1 -1
- package/package.json +1 -2
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/coverage-final.json +0 -12
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -176
- package/coverage/lib/index.html +0 -116
- package/coverage/lib/preload-script.js.html +0 -964
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -196
- package/coverage/src/collections/index.html +0 -116
- package/coverage/src/collections/runner-messages.ts.html +0 -312
- package/coverage/src/components/claude-manager.ts.html +0 -1290
- package/coverage/src/components/index.html +0 -146
- package/coverage/src/components/message-handler.ts.html +0 -730
- package/coverage/src/components/repository-manager.ts.html +0 -841
- package/coverage/src/index.html +0 -131
- package/coverage/src/index.ts.html +0 -448
- package/coverage/src/runner.ts.html +0 -1239
- package/coverage/src/utils/config.ts.html +0 -780
- package/coverage/src/utils/console.ts.html +0 -121
- package/coverage/src/utils/index.html +0 -161
- package/coverage/src/utils/logger.ts.html +0 -475
- package/coverage/src/utils/status-line.ts.html +0 -445
- package/exceptions.log +0 -24
- package/lib/codex-sdk/src/codex.ts +0 -38
- package/lib/codex-sdk/src/codexOptions.ts +0 -10
- package/lib/codex-sdk/src/events.ts +0 -80
- package/lib/codex-sdk/src/exec.ts +0 -336
- package/lib/codex-sdk/src/index.ts +0 -39
- package/lib/codex-sdk/src/items.ts +0 -127
- package/lib/codex-sdk/src/outputSchemaFile.ts +0 -40
- package/lib/codex-sdk/src/thread.ts +0 -155
- package/lib/codex-sdk/src/threadOptions.ts +0 -18
- package/lib/codex-sdk/src/turnOptions.ts +0 -6
- package/lib/codex-sdk/tests/abort.test.ts +0 -165
- package/lib/codex-sdk/tests/codexExecSpy.ts +0 -37
- package/lib/codex-sdk/tests/responsesProxy.ts +0 -225
- package/lib/codex-sdk/tests/run.test.ts +0 -687
- package/lib/codex-sdk/tests/runStreamed.test.ts +0 -211
- package/lib/codex-sdk/tsconfig.json +0 -24
- package/rejections.log +0 -68
- package/runner.log +0 -488
- package/src/components/claude-sdk-manager.ts +0 -1425
- package/src/components/codex-sdk-manager.ts +0 -1358
- package/src/components/enhanced-repository-manager.ts +0 -823
- package/src/components/message-handler-sse.ts +0 -1097
- package/src/components/repository-manager.ts +0 -337
- package/src/index.ts +0 -168
- package/src/runner-sse.ts +0 -917
- package/src/services/RunnerAPIClient.ts +0 -175
- package/src/services/SSEClient.ts +0 -258
- package/src/types/claude.ts +0 -66
- package/src/types/computer-name.d.ts +0 -4
- package/src/types/index.ts +0 -64
- package/src/types/messages.ts +0 -39
- package/src/types/runner-interface.ts +0 -36
- package/src/utils/StateManager.ts +0 -187
- package/src/utils/config.ts +0 -316
- package/src/utils/console.ts +0 -15
- package/src/utils/debug.ts +0 -18
- package/src/utils/expand-env.ts +0 -22
- package/src/utils/logger.ts +0 -134
- package/src/utils/model.ts +0 -29
- package/src/utils/status-line.ts +0 -122
- package/src/utils/tool-response-sanitizer.ts +0 -160
- package/test-debug.sh +0 -26
- package/tests/retry-strategies.test.ts +0 -410
- package/tests/sdk-integration.test.ts +0 -329
- package/tests/sdk-streaming.test.ts +0 -1180
- package/tests/setup.ts +0 -5
- package/tests/test-claude-manager.ts +0 -120
- package/tests/tool-response-sanitizer.test.ts +0 -63
- package/tsconfig.json +0 -36
- package/vitest.config.ts +0 -27
|
@@ -1,1097 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MessageHandler - Processes incoming JSONRPC messages from SSE events
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { IRunnerApp } from "../types/runner-interface";
|
|
6
|
-
import {
|
|
7
|
-
RunnerMessage,
|
|
8
|
-
MethodHandler,
|
|
9
|
-
ConversationContext,
|
|
10
|
-
ConversationConfig,
|
|
11
|
-
} from "../types";
|
|
12
|
-
import { SSEClient, SSEEvent } from "../services/SSEClient";
|
|
13
|
-
import { RunnerAPIClient } from "../services/RunnerAPIClient";
|
|
14
|
-
import { statusLineManager } from "../utils/status-line";
|
|
15
|
-
import { console } from "../utils/console";
|
|
16
|
-
import { createLogger } from "../utils/logger";
|
|
17
|
-
import { isRunnerDebugEnabled } from "../utils/debug";
|
|
18
|
-
|
|
19
|
-
const logger = createLogger("MessageHandler");
|
|
20
|
-
|
|
21
|
-
export class MessageHandler {
|
|
22
|
-
private methodHandlers: Map<string, MethodHandler>;
|
|
23
|
-
private runner: IRunnerApp;
|
|
24
|
-
private processedMessages: Set<string> = new Set();
|
|
25
|
-
private sseClient: SSEClient | null = null;
|
|
26
|
-
private apiClient: RunnerAPIClient;
|
|
27
|
-
private isProcessing: boolean = false;
|
|
28
|
-
|
|
29
|
-
constructor(runner: IRunnerApp) {
|
|
30
|
-
this.runner = runner;
|
|
31
|
-
this.methodHandlers = new Map();
|
|
32
|
-
this.apiClient = new RunnerAPIClient(runner.config_);
|
|
33
|
-
this.registerHandlers();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async startProcessing(): Promise<void> {
|
|
37
|
-
console.log("MessageHandler: Starting message processing with SSE...");
|
|
38
|
-
|
|
39
|
-
if (this.isProcessing) {
|
|
40
|
-
logger.warn("Message processing already started");
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
this.isProcessing = true;
|
|
45
|
-
|
|
46
|
-
// Update API client with runner ID if available
|
|
47
|
-
const runnerId = this.runner.getRunnerId();
|
|
48
|
-
if (runnerId) {
|
|
49
|
-
this.apiClient.setRunnerId(runnerId);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Catch up on missed messages first
|
|
53
|
-
await this.catchUpMissedMessages();
|
|
54
|
-
|
|
55
|
-
// Start SSE connection
|
|
56
|
-
await this.connectSSE();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async stopProcessing(): Promise<void> {
|
|
60
|
-
console.log("MessageHandler: Stopping message processing...");
|
|
61
|
-
|
|
62
|
-
this.isProcessing = false;
|
|
63
|
-
|
|
64
|
-
// Stop SSE client
|
|
65
|
-
if (this.sseClient) {
|
|
66
|
-
this.sseClient.stop();
|
|
67
|
-
this.sseClient = null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Clear processed messages tracking
|
|
71
|
-
this.processedMessages.clear();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Catch up on missed messages since lastProcessedAt
|
|
76
|
-
*/
|
|
77
|
-
private async catchUpMissedMessages(): Promise<void> {
|
|
78
|
-
const lastProcessedAt = this.runner.getLastProcessedAt();
|
|
79
|
-
|
|
80
|
-
if (!lastProcessedAt) {
|
|
81
|
-
logger.debug("No lastProcessedAt timestamp, skipping catch-up");
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
console.log(
|
|
86
|
-
`MessageHandler: Catching up on messages since ${lastProcessedAt.toISOString()}`
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const messages = await this.apiClient.fetchMissedMessages({
|
|
91
|
-
since: lastProcessedAt,
|
|
92
|
-
limit: 1000,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
if (messages.length > 0) {
|
|
96
|
-
console.log(
|
|
97
|
-
`MessageHandler: Processing ${messages.length} missed messages`
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
// Process messages in order
|
|
101
|
-
for (const message of messages) {
|
|
102
|
-
await this.processMessage(message);
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
console.log("MessageHandler: No missed messages to process");
|
|
106
|
-
}
|
|
107
|
-
} catch (error) {
|
|
108
|
-
logger.error("Failed to catch up on missed messages:", error);
|
|
109
|
-
// Continue anyway - SSE connection might still work
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Connect to SSE endpoint
|
|
115
|
-
*/
|
|
116
|
-
private async connectSSE(): Promise<void> {
|
|
117
|
-
const runnerId = this.runner.getRunnerId();
|
|
118
|
-
if (!runnerId) {
|
|
119
|
-
throw new Error("Cannot connect to SSE without runner ID");
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const token = process.env["NORTHFLARE_RUNNER_TOKEN"];
|
|
123
|
-
if (!token) {
|
|
124
|
-
throw new Error("Missing NORTHFLARE_RUNNER_TOKEN");
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
console.log("MessageHandler: Connecting to SSE endpoint...");
|
|
128
|
-
|
|
129
|
-
this.sseClient = new SSEClient({
|
|
130
|
-
url: `${this.runner.config_.orchestratorUrl}/api/runner-events`,
|
|
131
|
-
runnerId,
|
|
132
|
-
token,
|
|
133
|
-
onMessage: this.handleSSEEvent.bind(this),
|
|
134
|
-
onError: (error) => {
|
|
135
|
-
logger.error("SSE connection error:", error);
|
|
136
|
-
},
|
|
137
|
-
onConnect: () => {
|
|
138
|
-
console.log("MessageHandler: SSE connection established");
|
|
139
|
-
},
|
|
140
|
-
onDisconnect: () => {
|
|
141
|
-
console.log("MessageHandler: SSE connection closed");
|
|
142
|
-
},
|
|
143
|
-
reconnectInterval: 1000,
|
|
144
|
-
maxReconnectInterval: 30000,
|
|
145
|
-
reconnectMultiplier: 2,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// Connect with lastProcessedAt for server-side filtering
|
|
149
|
-
const lastProcessedAt = this.runner.getLastProcessedAt();
|
|
150
|
-
await this.sseClient.connect(lastProcessedAt?.toISOString());
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Handle SSE event
|
|
155
|
-
*/
|
|
156
|
-
private async handleSSEEvent(event: SSEEvent): Promise<void> {
|
|
157
|
-
if (event.type === "runner.message") {
|
|
158
|
-
const message = event.data as RunnerMessage;
|
|
159
|
-
|
|
160
|
-
if (isRunnerDebugEnabled()) {
|
|
161
|
-
logger.debug("Received SSE event", {
|
|
162
|
-
eventId: event.id,
|
|
163
|
-
type: event.type,
|
|
164
|
-
messageId: message?.id,
|
|
165
|
-
method: message?.payload?.method,
|
|
166
|
-
createdAt: message?.createdAt,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
// Process the message
|
|
172
|
-
await this.processMessage(message);
|
|
173
|
-
} catch (err) {
|
|
174
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
175
|
-
logger.error("Failed to process runner.message:", msg);
|
|
176
|
-
// Do not crash the runner on a single bad message
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
} else if (event.type === "connection.established") {
|
|
180
|
-
logger.debug("SSE connection established", event.data);
|
|
181
|
-
} else {
|
|
182
|
-
logger.debug(`Received event type: ${event.type}`, event.data);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
private async processMessage(message: RunnerMessage): Promise<void> {
|
|
187
|
-
if (isRunnerDebugEnabled()) {
|
|
188
|
-
logger.debug("processMessage called", {
|
|
189
|
-
messageId: message.id,
|
|
190
|
-
method: message.payload?.method,
|
|
191
|
-
direction: message.direction,
|
|
192
|
-
createdAt: message.createdAt,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Skip if already processed
|
|
197
|
-
if (this.processedMessages.has(message.id)) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Check if we should process this message based on ownership
|
|
202
|
-
if (!this.shouldProcessMessage(message)) {
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const { method, params } = message.payload;
|
|
207
|
-
|
|
208
|
-
if (!method) {
|
|
209
|
-
await this.sendError(message, "Missing method in message payload");
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const handler = this.methodHandlers.get(method);
|
|
214
|
-
|
|
215
|
-
if (!handler) {
|
|
216
|
-
await this.sendError(message, `Unknown method: ${method}`);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (isRunnerDebugEnabled()) {
|
|
221
|
-
logger.debug("Processing message", {
|
|
222
|
-
messageId: message.id,
|
|
223
|
-
method: method,
|
|
224
|
-
taskId: message.taskId,
|
|
225
|
-
isActionMessage: this.isActionMessage(message),
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
try {
|
|
230
|
-
await handler(params, message);
|
|
231
|
-
await this.markProcessed(message);
|
|
232
|
-
|
|
233
|
-
// Acknowledge ALL messages to update lastProcessedAt
|
|
234
|
-
await this.acknowledgeMessage(message);
|
|
235
|
-
|
|
236
|
-
if (isRunnerDebugEnabled()) {
|
|
237
|
-
logger.debug("Message acknowledged", {
|
|
238
|
-
messageId: message.id,
|
|
239
|
-
method: method,
|
|
240
|
-
timestamp: message.createdAt,
|
|
241
|
-
wasActionMessage: this.isActionMessage(message),
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
} catch (error) {
|
|
245
|
-
if (isRunnerDebugEnabled()) {
|
|
246
|
-
logger.debug("Message processing error", {
|
|
247
|
-
messageId: message.id,
|
|
248
|
-
method: method,
|
|
249
|
-
error: error instanceof Error ? error.message : String(error),
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
await this.handleError(message, error);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
private shouldProcessMessage(message: RunnerMessage): boolean {
|
|
257
|
-
const decision = (() => {
|
|
258
|
-
// Always process our own responses going to orchestrator
|
|
259
|
-
if (message.direction === "to_orchestrator") {
|
|
260
|
-
return { shouldProcess: true, reason: "own response to orchestrator" };
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Always process UID change messages BEFORE checking timestamp
|
|
264
|
-
// This is critical because UID changes can update lastProcessedAt itself
|
|
265
|
-
if (message.payload?.method === "runner.uid.changed") {
|
|
266
|
-
return { shouldProcess: true, reason: "UID change message" };
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Filter by lastProcessedAt (after checking for UID change messages)
|
|
270
|
-
const lastProcessedAt = this.runner.getLastProcessedAt();
|
|
271
|
-
if (lastProcessedAt && message.createdAt) {
|
|
272
|
-
const messageTime = new Date(message.createdAt);
|
|
273
|
-
if (messageTime <= lastProcessedAt) {
|
|
274
|
-
return {
|
|
275
|
-
shouldProcess: false,
|
|
276
|
-
reason: "message before lastProcessedAt",
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// If we're not the active runner
|
|
282
|
-
if (!this.runner.getIsActiveRunner()) {
|
|
283
|
-
// Only process if it's for a pre-handoff conversation by conversationId
|
|
284
|
-
const cid =
|
|
285
|
-
message.conversationId || message.payload?.params?.conversationId;
|
|
286
|
-
if (cid && this.runner.getPreHandoffConversations().has(cid)) {
|
|
287
|
-
return {
|
|
288
|
-
shouldProcess: true,
|
|
289
|
-
reason: "pre-handoff conversation (by conversationId)",
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
return { shouldProcess: false, reason: "not active runner" };
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// We're active and message is after lastProcessedAt
|
|
296
|
-
return {
|
|
297
|
-
shouldProcess: true,
|
|
298
|
-
reason: "active runner, message after watermark",
|
|
299
|
-
};
|
|
300
|
-
})();
|
|
301
|
-
|
|
302
|
-
if (isRunnerDebugEnabled()) {
|
|
303
|
-
logger.debug("Message processing decision", {
|
|
304
|
-
messageId: message.id,
|
|
305
|
-
method: message.payload?.method,
|
|
306
|
-
shouldProcess: decision.shouldProcess,
|
|
307
|
-
reason: decision.reason,
|
|
308
|
-
runnerUid: this.runner.getRunnerUid(),
|
|
309
|
-
isActiveRunner: this.runner.getIsActiveRunner(),
|
|
310
|
-
lastProcessedAt:
|
|
311
|
-
this.runner.getLastProcessedAt()?.toISOString() || "null",
|
|
312
|
-
messageCreatedAt: message.createdAt,
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return decision.shouldProcess;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
private isActionMessage(message: RunnerMessage): boolean {
|
|
320
|
-
const actionMethods = [
|
|
321
|
-
"conversation.start",
|
|
322
|
-
"conversation.stop",
|
|
323
|
-
"conversation.resume",
|
|
324
|
-
"conversation.config",
|
|
325
|
-
"message.user",
|
|
326
|
-
];
|
|
327
|
-
return actionMethods.includes(message.payload?.method || "");
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
private async acknowledgeMessage(message: RunnerMessage): Promise<void> {
|
|
331
|
-
const runnerId = this.runner.getRunnerId();
|
|
332
|
-
console.log(`[MessageHandler] Sending message.acknowledge:`, {
|
|
333
|
-
runnerId,
|
|
334
|
-
messageTimestamp: message.createdAt,
|
|
335
|
-
messageId: message.id,
|
|
336
|
-
method: message.payload?.method,
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
try {
|
|
340
|
-
await this.apiClient.acknowledgeMessage(message.createdAt);
|
|
341
|
-
|
|
342
|
-
// Update local lastProcessedAt
|
|
343
|
-
await this.runner.updateLastProcessedAt!(new Date(message.createdAt));
|
|
344
|
-
|
|
345
|
-
console.log(`[MessageHandler] ✅ message.acknowledge sent successfully`);
|
|
346
|
-
} catch (error) {
|
|
347
|
-
logger.error("Failed to acknowledge message:", error);
|
|
348
|
-
// Continue processing even if acknowledgment fails
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
private async markProcessed(message: RunnerMessage): Promise<void> {
|
|
353
|
-
// Track processed messages internally
|
|
354
|
-
this.processedMessages.add(message.id);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
private async sendError(
|
|
358
|
-
message: RunnerMessage,
|
|
359
|
-
errorMessage: string
|
|
360
|
-
): Promise<void> {
|
|
361
|
-
// Send error report with conversation object info if available
|
|
362
|
-
const conversationObjectType =
|
|
363
|
-
message.conversationObjectType || (message.taskId ? "Task" : undefined);
|
|
364
|
-
const conversationObjectId = message.conversationObjectId || message.taskId;
|
|
365
|
-
|
|
366
|
-
if (!conversationObjectId) {
|
|
367
|
-
console.error(
|
|
368
|
-
`[MessageHandler] Cannot send error report - no conversationObjectId available. Error: ${errorMessage}`
|
|
369
|
-
);
|
|
370
|
-
if (isRunnerDebugEnabled()) {
|
|
371
|
-
logger.debug("Error without conversationObjectId", {
|
|
372
|
-
messageId: message.id,
|
|
373
|
-
method: message.payload?.method,
|
|
374
|
-
error: errorMessage,
|
|
375
|
-
runnerId: message.runnerId,
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
await this.runner.notify("error.report", {
|
|
382
|
-
conversationObjectType,
|
|
383
|
-
conversationObjectId,
|
|
384
|
-
messageId: message.id,
|
|
385
|
-
errorType: "method_error",
|
|
386
|
-
message: errorMessage,
|
|
387
|
-
details: {
|
|
388
|
-
originalMessage: message,
|
|
389
|
-
timestamp: new Date(),
|
|
390
|
-
},
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
private async handleError(message: RunnerMessage, error: any): Promise<void> {
|
|
395
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
396
|
-
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
397
|
-
|
|
398
|
-
// Send error report with conversation object info if available
|
|
399
|
-
const conversationObjectType =
|
|
400
|
-
message.conversationObjectType || (message.taskId ? "Task" : undefined);
|
|
401
|
-
const conversationObjectId = message.conversationObjectId || message.taskId;
|
|
402
|
-
|
|
403
|
-
if (!conversationObjectId) {
|
|
404
|
-
console.error(
|
|
405
|
-
`[MessageHandler] Cannot send error report - no conversationObjectId available. Processing error: ${errorMessage}`
|
|
406
|
-
);
|
|
407
|
-
if (isRunnerDebugEnabled()) {
|
|
408
|
-
logger.debug("Processing error without conversationObjectId", {
|
|
409
|
-
messageId: message.id,
|
|
410
|
-
method: message.payload?.method,
|
|
411
|
-
error: errorMessage,
|
|
412
|
-
stack: errorStack,
|
|
413
|
-
runnerId: message.runnerId,
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
await this.runner.notify("error.report", {
|
|
420
|
-
conversationObjectType,
|
|
421
|
-
conversationObjectId,
|
|
422
|
-
messageId: message.id,
|
|
423
|
-
errorType: "processing_error",
|
|
424
|
-
message: errorMessage,
|
|
425
|
-
details: {
|
|
426
|
-
stack: errorStack,
|
|
427
|
-
originalMessage: message,
|
|
428
|
-
timestamp: new Date(),
|
|
429
|
-
},
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
private registerHandlers(): void {
|
|
434
|
-
console.log("MessageHandler: Registering handlers...");
|
|
435
|
-
|
|
436
|
-
this.methodHandlers = new Map([
|
|
437
|
-
["conversation.start", this.handleConversationStart.bind(this)],
|
|
438
|
-
["conversation.stop", this.handleConversationStop.bind(this)],
|
|
439
|
-
["conversation.resume", this.handleConversationResume.bind(this)],
|
|
440
|
-
["conversation.config", this.handleConversationConfig.bind(this)],
|
|
441
|
-
["message.user", this.handleUserMessage.bind(this)],
|
|
442
|
-
["runner.uid.changed", this.handleUidChanged.bind(this)],
|
|
443
|
-
["git.operation", this.handleGitOperation.bind(this)],
|
|
444
|
-
["git.cleanup", this.handleGitCleanup.bind(this)],
|
|
445
|
-
]);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// All the handler methods remain the same as in the original file
|
|
449
|
-
// Just copy them from the original message-handler.ts starting from line 386
|
|
450
|
-
|
|
451
|
-
private async handleConversationStart(
|
|
452
|
-
params: any,
|
|
453
|
-
message: RunnerMessage
|
|
454
|
-
): Promise<void> {
|
|
455
|
-
const {
|
|
456
|
-
conversationObjectType = message.conversationObjectType || "Task",
|
|
457
|
-
conversationObjectId = message.conversationObjectId ||
|
|
458
|
-
params.conversation?.objectId,
|
|
459
|
-
config,
|
|
460
|
-
initialMessages,
|
|
461
|
-
conversation,
|
|
462
|
-
} = params;
|
|
463
|
-
|
|
464
|
-
// Validate required parameters
|
|
465
|
-
if (!config) {
|
|
466
|
-
throw new Error("Missing required parameter: config");
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// Require a conversation object
|
|
470
|
-
const conversationData = conversation;
|
|
471
|
-
if (!conversationData) {
|
|
472
|
-
throw new Error(
|
|
473
|
-
"Missing required parameter: conversation object must be provided"
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
const finalObjectType =
|
|
478
|
-
conversationObjectType || conversationData.objectType;
|
|
479
|
-
const finalObjectId = conversationObjectId || conversationData.objectId;
|
|
480
|
-
if (!finalObjectId) {
|
|
481
|
-
throw new Error("Missing conversationObjectId");
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Debug log the config
|
|
485
|
-
console.log(`[MessageHandler] conversation.start config:`, {
|
|
486
|
-
conversationObjectId,
|
|
487
|
-
hasConfig: !!config,
|
|
488
|
-
hasRepository: !!config?.repository,
|
|
489
|
-
repositoryType: config?.repository?.type,
|
|
490
|
-
repositoryUrl: config?.repository?.url,
|
|
491
|
-
workspaceId: config?.workspaceId,
|
|
492
|
-
fullConfig: JSON.stringify(config, null, 2),
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
// Debug log workspace instructions received
|
|
496
|
-
console.log(
|
|
497
|
-
`[MessageHandler] Received conversation with workspaceInstructions:`,
|
|
498
|
-
{
|
|
499
|
-
conversationId: conversationData.id,
|
|
500
|
-
hasWorkspaceInstructions: !!conversationData.workspaceInstructions,
|
|
501
|
-
workspaceInstructionsLength: conversationData.workspaceInstructions?.length ?? 0,
|
|
502
|
-
workspaceInstructionsPreview: conversationData.workspaceInstructions?.slice(0, 100),
|
|
503
|
-
hasGlobalInstructions: !!conversationData.globalInstructions,
|
|
504
|
-
globalInstructionsLength: conversationData.globalInstructions?.length ?? 0,
|
|
505
|
-
}
|
|
506
|
-
);
|
|
507
|
-
|
|
508
|
-
const provider = this.resolveAgentProvider(conversationData, config);
|
|
509
|
-
const manager = this.getManagerForProvider(provider);
|
|
510
|
-
|
|
511
|
-
// Start the conversation with the provided/loaded conversation details
|
|
512
|
-
await manager.startConversation(
|
|
513
|
-
finalObjectType,
|
|
514
|
-
finalObjectId,
|
|
515
|
-
config,
|
|
516
|
-
initialMessages || [],
|
|
517
|
-
conversationData
|
|
518
|
-
);
|
|
519
|
-
|
|
520
|
-
// Update status line
|
|
521
|
-
statusLineManager.updateActiveCount(this.runner.activeConversations_.size);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
private async handleConversationStop(
|
|
525
|
-
params: any,
|
|
526
|
-
message: RunnerMessage
|
|
527
|
-
): Promise<void> {
|
|
528
|
-
// Require conversationId (present at message level); do not fall back to agentSessionId/taskId
|
|
529
|
-
const { conversationId = message.conversationId, reason } = params;
|
|
530
|
-
|
|
531
|
-
console.log(`[MessageHandler] handleConversationStop called with:`, {
|
|
532
|
-
conversationId,
|
|
533
|
-
messageConversationId: message.conversationId,
|
|
534
|
-
agentSessionId: params?.agentSessionId,
|
|
535
|
-
taskId: params?.taskId,
|
|
536
|
-
params: JSON.stringify(params),
|
|
537
|
-
activeConversations: this.runner.activeConversations_.size,
|
|
538
|
-
conversationIds: Array.from(this.runner.activeConversations_.keys()),
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
// Lookup strictly by conversationId
|
|
542
|
-
let context: ConversationContext | undefined;
|
|
543
|
-
let targetConversationId: string | undefined;
|
|
544
|
-
if (conversationId) {
|
|
545
|
-
context = this.runner.getConversationContext(conversationId);
|
|
546
|
-
targetConversationId = conversationId;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
console.log(`[MessageHandler] handleConversationStop lookup result:`, {
|
|
550
|
-
contextFound: !!context,
|
|
551
|
-
targetConversationId,
|
|
552
|
-
contextTaskId: context?.taskId,
|
|
553
|
-
contextAgentSessionId: context?.agentSessionId,
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
// Check if we have any identifier to work with
|
|
557
|
-
if (!conversationId) {
|
|
558
|
-
throw new Error("Missing required parameter: conversationId");
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
if (context && targetConversationId) {
|
|
562
|
-
context.status = "stopping";
|
|
563
|
-
const manager = this.getManagerForConversationContext(context);
|
|
564
|
-
await manager.stopConversation(
|
|
565
|
-
context.agentSessionId,
|
|
566
|
-
context,
|
|
567
|
-
false, // Not a runner shutdown
|
|
568
|
-
reason // Pass the reason through
|
|
569
|
-
);
|
|
570
|
-
context.status = "stopped";
|
|
571
|
-
this.runner.activeConversations_.delete(targetConversationId);
|
|
572
|
-
|
|
573
|
-
// Update status line
|
|
574
|
-
statusLineManager.updateActiveCount(
|
|
575
|
-
this.runner.activeConversations_.size
|
|
576
|
-
);
|
|
577
|
-
} else {
|
|
578
|
-
// No conversation found - this is expected as conversations may have already ended
|
|
579
|
-
// or been cleaned up. Just log it and update status line.
|
|
580
|
-
console.log(
|
|
581
|
-
`Conversation stop requested for ${conversationId} - conversation not found or already cleaned up`
|
|
582
|
-
);
|
|
583
|
-
|
|
584
|
-
// If we have a targetConversationId, ensure it's removed from tracking
|
|
585
|
-
if (targetConversationId) {
|
|
586
|
-
this.runner.activeConversations_.delete(targetConversationId);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
statusLineManager.updateActiveCount(
|
|
590
|
-
this.runner.activeConversations_.size
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
private async handleConversationResume(
|
|
596
|
-
params: any,
|
|
597
|
-
message: RunnerMessage
|
|
598
|
-
): Promise<void> {
|
|
599
|
-
const {
|
|
600
|
-
conversationId = message.conversationId,
|
|
601
|
-
conversation: resumeConversation,
|
|
602
|
-
config,
|
|
603
|
-
message: resumeMessage,
|
|
604
|
-
} = params;
|
|
605
|
-
|
|
606
|
-
const cid = params.conversationId || conversationId;
|
|
607
|
-
if (!cid) {
|
|
608
|
-
throw new Error("Missing required parameter: conversationId");
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// Require conversation details
|
|
612
|
-
const conversationData = resumeConversation;
|
|
613
|
-
if (!conversationData) {
|
|
614
|
-
throw new Error(
|
|
615
|
-
"Missing required parameter: conversation object must be provided"
|
|
616
|
-
);
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
const agentSessionId = conversationData.agentSessionId;
|
|
620
|
-
if (!agentSessionId) {
|
|
621
|
-
throw new Error("Cannot resume conversation without agentSessionId");
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
const provider = this.resolveAgentProvider(conversationData, config);
|
|
625
|
-
const manager = this.getManagerForProvider(provider);
|
|
626
|
-
await manager.resumeConversation(
|
|
627
|
-
conversationData.objectType,
|
|
628
|
-
conversationData.objectId,
|
|
629
|
-
agentSessionId,
|
|
630
|
-
config,
|
|
631
|
-
conversationData,
|
|
632
|
-
resumeMessage
|
|
633
|
-
);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
private async handleConversationConfig(
|
|
637
|
-
params: any,
|
|
638
|
-
message: RunnerMessage
|
|
639
|
-
): Promise<void> {
|
|
640
|
-
const { conversationId, model, permissionsMode, config } = params;
|
|
641
|
-
|
|
642
|
-
console.log(`[MessageHandler] handleConversationConfig called with:`, {
|
|
643
|
-
conversationId,
|
|
644
|
-
model,
|
|
645
|
-
permissionsMode,
|
|
646
|
-
hasConfig: !!config,
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
// Find the active conversation by conversationId only
|
|
650
|
-
let context: ConversationContext | undefined;
|
|
651
|
-
if (conversationId) {
|
|
652
|
-
context = this.runner.getConversationContext(conversationId);
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
if (!context) {
|
|
656
|
-
throw new Error(
|
|
657
|
-
`No active conversation found for conversationId: ${conversationId}`
|
|
658
|
-
);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// Validate required parameters
|
|
662
|
-
if (!model && !permissionsMode) {
|
|
663
|
-
throw new Error(
|
|
664
|
-
"At least one of model or permissionsMode must be provided"
|
|
665
|
-
);
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// Update the config with new values
|
|
669
|
-
const newConfig = {
|
|
670
|
-
...context.config,
|
|
671
|
-
...(model && { model }),
|
|
672
|
-
...(permissionsMode && { permissionsMode }),
|
|
673
|
-
...config, // Allow full config overrides if provided
|
|
674
|
-
};
|
|
675
|
-
|
|
676
|
-
console.log(
|
|
677
|
-
`[MessageHandler] Stopping conversation ${context.conversationId} to apply new config`
|
|
678
|
-
);
|
|
679
|
-
|
|
680
|
-
// Stop the current conversation
|
|
681
|
-
const manager = this.getManagerForConversationContext(context);
|
|
682
|
-
|
|
683
|
-
await manager.stopConversation(
|
|
684
|
-
context.agentSessionId,
|
|
685
|
-
context,
|
|
686
|
-
false // Not a runner shutdown, just updating config
|
|
687
|
-
);
|
|
688
|
-
|
|
689
|
-
// Remove from active conversations
|
|
690
|
-
this.runner.activeConversations_.delete(context.conversationId);
|
|
691
|
-
|
|
692
|
-
console.log(
|
|
693
|
-
`[MessageHandler] Resuming conversation ${context.conversationId} with new config`
|
|
694
|
-
);
|
|
695
|
-
|
|
696
|
-
// Resume with new config
|
|
697
|
-
await manager.resumeConversation(
|
|
698
|
-
context.conversationObjectType,
|
|
699
|
-
context.conversationObjectId,
|
|
700
|
-
context.agentSessionId,
|
|
701
|
-
newConfig,
|
|
702
|
-
{
|
|
703
|
-
id: context.conversationId,
|
|
704
|
-
objectType: context.conversationObjectType,
|
|
705
|
-
objectId: context.conversationObjectId,
|
|
706
|
-
model: newConfig.model,
|
|
707
|
-
globalInstructions: context.globalInstructions,
|
|
708
|
-
workspaceInstructions: context.workspaceInstructions,
|
|
709
|
-
permissionsMode: newConfig.permissionsMode,
|
|
710
|
-
agentSessionId: context.agentSessionId,
|
|
711
|
-
},
|
|
712
|
-
"<system-instructions>Configuration updated. Please continue with the new settings.</system-instructions>"
|
|
713
|
-
);
|
|
714
|
-
|
|
715
|
-
// Update status line
|
|
716
|
-
statusLineManager.updateActiveCount(this.runner.activeConversations_.size);
|
|
717
|
-
|
|
718
|
-
console.log(
|
|
719
|
-
`[MessageHandler] Conversation ${context.conversationId} config updated successfully`
|
|
720
|
-
);
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
private async handleUserMessage(
|
|
724
|
-
params: any,
|
|
725
|
-
message: RunnerMessage
|
|
726
|
-
): Promise<void> {
|
|
727
|
-
const {
|
|
728
|
-
conversationId,
|
|
729
|
-
content,
|
|
730
|
-
config,
|
|
731
|
-
conversationObjectType = message.conversationObjectType || "Task",
|
|
732
|
-
conversationObjectId = message.conversationObjectId,
|
|
733
|
-
conversation,
|
|
734
|
-
agentSessionId,
|
|
735
|
-
} = params;
|
|
736
|
-
|
|
737
|
-
// Validate required parameters
|
|
738
|
-
if (!conversationId) {
|
|
739
|
-
throw new Error("Missing required parameter: conversationId");
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
if (!content) {
|
|
743
|
-
throw new Error("Missing required parameter: content");
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
const existingContext = this.runner.getConversationContext(conversationId);
|
|
747
|
-
const manager = existingContext
|
|
748
|
-
? this.getManagerForConversationContext(existingContext)
|
|
749
|
-
: this.getManagerForProvider(
|
|
750
|
-
this.resolveAgentProvider(conversation, config)
|
|
751
|
-
);
|
|
752
|
-
|
|
753
|
-
await manager.sendUserMessage(
|
|
754
|
-
conversationId,
|
|
755
|
-
content,
|
|
756
|
-
config,
|
|
757
|
-
conversationObjectType,
|
|
758
|
-
conversationObjectId,
|
|
759
|
-
conversation,
|
|
760
|
-
agentSessionId
|
|
761
|
-
);
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
private async handleUidChanged(
|
|
765
|
-
params: any,
|
|
766
|
-
_message: RunnerMessage
|
|
767
|
-
): Promise<void> {
|
|
768
|
-
const { runnerUid, lastProcessedAt } = params;
|
|
769
|
-
|
|
770
|
-
console.log(
|
|
771
|
-
`MessageHandler: Handling UID change notification - new UID: ${runnerUid}, lastProcessedAt: ${lastProcessedAt}`
|
|
772
|
-
);
|
|
773
|
-
|
|
774
|
-
if (isRunnerDebugEnabled()) {
|
|
775
|
-
logger.debug("UID change notification received", {
|
|
776
|
-
newUid: runnerUid,
|
|
777
|
-
currentUid: this.runner.getRunnerUid(),
|
|
778
|
-
newLastProcessedAt: lastProcessedAt,
|
|
779
|
-
currentLastProcessedAt:
|
|
780
|
-
this.runner.getLastProcessedAt()?.toISOString() || "null",
|
|
781
|
-
wasActiveRunner: this.runner.getIsActiveRunner(),
|
|
782
|
-
activeConversations: this.runner.activeConversations_.size,
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
// Check if the UID matches ours FIRST, before checking timestamps
|
|
787
|
-
// This ensures we activate even if we received the message during catch-up
|
|
788
|
-
if (runnerUid === this.runner.getRunnerUid()) {
|
|
789
|
-
// This is our UID - we're the active runner
|
|
790
|
-
console.log("MessageHandler: We are now the active runner");
|
|
791
|
-
this.runner.setIsActiveRunner(true);
|
|
792
|
-
await this.runner.updateLastProcessedAt!(
|
|
793
|
-
lastProcessedAt ? new Date(lastProcessedAt) : null
|
|
794
|
-
);
|
|
795
|
-
|
|
796
|
-
if (isRunnerDebugEnabled()) {
|
|
797
|
-
logger.debug("Runner activated as primary", {
|
|
798
|
-
runnerUid: runnerUid,
|
|
799
|
-
lastProcessedAt: lastProcessedAt,
|
|
800
|
-
orchestratorUrl: this.runner.config_.orchestratorUrl,
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// Emit activation notification - wrap in try/catch to handle failures gracefully
|
|
805
|
-
try {
|
|
806
|
-
await this.runner.notify("runner.activate", {
|
|
807
|
-
runnerId: this.runner.getRunnerId(),
|
|
808
|
-
runnerUid: runnerUid,
|
|
809
|
-
});
|
|
810
|
-
} catch (error) {
|
|
811
|
-
console.error("Failed to send activation notification:", error);
|
|
812
|
-
if (isRunnerDebugEnabled()) {
|
|
813
|
-
logger.debug("Activation notification failed", {
|
|
814
|
-
error: error instanceof Error ? error.message : String(error),
|
|
815
|
-
});
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
// Start processing messages from the lastProcessedAt point
|
|
820
|
-
console.log(
|
|
821
|
-
`MessageHandler: Will process messages after ${
|
|
822
|
-
lastProcessedAt || "beginning"
|
|
823
|
-
}`
|
|
824
|
-
);
|
|
825
|
-
} else {
|
|
826
|
-
// Different UID - check if this is an old UID change that we should ignore
|
|
827
|
-
const currentLastProcessedAt = this.runner.getLastProcessedAt();
|
|
828
|
-
if (currentLastProcessedAt && lastProcessedAt) {
|
|
829
|
-
const newTime = new Date(lastProcessedAt);
|
|
830
|
-
if (newTime < currentLastProcessedAt) {
|
|
831
|
-
console.log(
|
|
832
|
-
`MessageHandler: Ignoring old UID change (${lastProcessedAt} < ${currentLastProcessedAt.toISOString()})`
|
|
833
|
-
);
|
|
834
|
-
if (isRunnerDebugEnabled()) {
|
|
835
|
-
logger.debug("Ignoring old UID change", {
|
|
836
|
-
newUid: runnerUid,
|
|
837
|
-
newLastProcessedAt: lastProcessedAt,
|
|
838
|
-
currentLastProcessedAt: currentLastProcessedAt.toISOString(),
|
|
839
|
-
});
|
|
840
|
-
}
|
|
841
|
-
return;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// Different UID - we're being replaced
|
|
846
|
-
console.log(
|
|
847
|
-
`MessageHandler: We are being replaced by runner with UID ${runnerUid}`
|
|
848
|
-
);
|
|
849
|
-
this.runner.setIsActiveRunner(false);
|
|
850
|
-
|
|
851
|
-
// Remember which conversations were active before handoff (by conversationId)
|
|
852
|
-
for (const [conversationId, context] of this.runner
|
|
853
|
-
.activeConversations_) {
|
|
854
|
-
if (context.status === "active") {
|
|
855
|
-
this.runner.getPreHandoffConversations().add(conversationId);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
if (isRunnerDebugEnabled()) {
|
|
860
|
-
logger.debug("Runner deactivated - being replaced", {
|
|
861
|
-
newRunnerUid: runnerUid,
|
|
862
|
-
ourUid: this.runner.getRunnerUid(),
|
|
863
|
-
preHandoffConversations: Array.from(
|
|
864
|
-
this.runner.getPreHandoffConversations()
|
|
865
|
-
),
|
|
866
|
-
activeConversationsCount:
|
|
867
|
-
this.runner.getPreHandoffConversations().size,
|
|
868
|
-
});
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
console.log(
|
|
872
|
-
`MessageHandler: Will complete ${
|
|
873
|
-
this.runner.getPreHandoffConversations().size
|
|
874
|
-
} active conversations`
|
|
875
|
-
);
|
|
876
|
-
|
|
877
|
-
// Emit deactivation notification - wrap in try/catch to handle failures gracefully
|
|
878
|
-
try {
|
|
879
|
-
await this.runner.notify("runner.deactivate", {
|
|
880
|
-
runnerId: this.runner.getRunnerId(),
|
|
881
|
-
runnerUid: this.runner.getRunnerUid(),
|
|
882
|
-
activeConversations: this.runner.getPreHandoffConversations().size,
|
|
883
|
-
});
|
|
884
|
-
} catch (error) {
|
|
885
|
-
console.error("Failed to send deactivation notification:", error);
|
|
886
|
-
if (isRunnerDebugEnabled()) {
|
|
887
|
-
logger.debug("Deactivation notification failed", {
|
|
888
|
-
error: error instanceof Error ? error.message : String(error),
|
|
889
|
-
});
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
private async handleGitOperation(
|
|
896
|
-
params: any,
|
|
897
|
-
message: RunnerMessage
|
|
898
|
-
): Promise<void> {
|
|
899
|
-
const { taskId, operation, params: opParams } = params;
|
|
900
|
-
|
|
901
|
-
if (!taskId || !operation) {
|
|
902
|
-
throw new Error("Missing required parameters: taskId and operation");
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
console.log(
|
|
906
|
-
`MessageHandler: Handling Git operation ${operation} for task ${taskId}`
|
|
907
|
-
);
|
|
908
|
-
|
|
909
|
-
const repoManager = this.runner.repositoryManager_;
|
|
910
|
-
|
|
911
|
-
try {
|
|
912
|
-
switch (operation) {
|
|
913
|
-
case "stage":
|
|
914
|
-
await repoManager.stageAll(taskId);
|
|
915
|
-
await this.sendGitStateUpdate(taskId);
|
|
916
|
-
break;
|
|
917
|
-
|
|
918
|
-
case "commit":
|
|
919
|
-
if (!opParams?.message) {
|
|
920
|
-
throw new Error("Commit message is required");
|
|
921
|
-
}
|
|
922
|
-
const commitHash = await repoManager.commit(
|
|
923
|
-
taskId,
|
|
924
|
-
opParams.message,
|
|
925
|
-
opParams.author
|
|
926
|
-
);
|
|
927
|
-
await this.recordGitOperation(taskId, "commit", "succeeded", {
|
|
928
|
-
commitHash,
|
|
929
|
-
});
|
|
930
|
-
await this.sendGitStateUpdate(taskId);
|
|
931
|
-
break;
|
|
932
|
-
|
|
933
|
-
case "push":
|
|
934
|
-
// TODO: Implement push operation
|
|
935
|
-
console.log("Push operation not yet implemented");
|
|
936
|
-
break;
|
|
937
|
-
|
|
938
|
-
case "rebase":
|
|
939
|
-
if (!opParams?.targetBranch) {
|
|
940
|
-
throw new Error("Target branch is required for rebase");
|
|
941
|
-
}
|
|
942
|
-
const rebaseResult = await repoManager.rebaseTask(
|
|
943
|
-
taskId,
|
|
944
|
-
opParams.targetBranch
|
|
945
|
-
);
|
|
946
|
-
await this.recordGitOperation(
|
|
947
|
-
taskId,
|
|
948
|
-
"rebase",
|
|
949
|
-
rebaseResult.success ? "succeeded" : "failed",
|
|
950
|
-
rebaseResult
|
|
951
|
-
);
|
|
952
|
-
await this.sendGitStateUpdate(taskId);
|
|
953
|
-
break;
|
|
954
|
-
|
|
955
|
-
case "merge":
|
|
956
|
-
if (!opParams?.targetBranch) {
|
|
957
|
-
throw new Error("Target branch is required for merge");
|
|
958
|
-
}
|
|
959
|
-
const mergeResult = await repoManager.mergeTask(
|
|
960
|
-
taskId,
|
|
961
|
-
opParams.targetBranch,
|
|
962
|
-
opParams.mode
|
|
963
|
-
);
|
|
964
|
-
await this.recordGitOperation(
|
|
965
|
-
taskId,
|
|
966
|
-
"merge",
|
|
967
|
-
mergeResult.success ? "succeeded" : "failed",
|
|
968
|
-
mergeResult
|
|
969
|
-
);
|
|
970
|
-
await this.sendGitStateUpdate(taskId);
|
|
971
|
-
break;
|
|
972
|
-
|
|
973
|
-
default:
|
|
974
|
-
throw new Error(`Unknown Git operation: ${operation}`);
|
|
975
|
-
}
|
|
976
|
-
} catch (error) {
|
|
977
|
-
console.error(
|
|
978
|
-
`Git operation ${operation} failed for task ${taskId}:`,
|
|
979
|
-
error
|
|
980
|
-
);
|
|
981
|
-
await this.recordGitOperation(taskId, operation, "failed", {
|
|
982
|
-
error: error instanceof Error ? error.message : String(error),
|
|
983
|
-
});
|
|
984
|
-
throw error;
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
private async handleGitCleanup(
|
|
989
|
-
params: any,
|
|
990
|
-
message: RunnerMessage
|
|
991
|
-
): Promise<void> {
|
|
992
|
-
const { taskId, preserveBranch = false } = params;
|
|
993
|
-
|
|
994
|
-
if (!taskId) {
|
|
995
|
-
throw new Error("Missing required parameter: taskId");
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
console.log(`MessageHandler: Cleaning up Git worktree for task ${taskId}`);
|
|
999
|
-
|
|
1000
|
-
const repoManager = this.runner.repositoryManager_;
|
|
1001
|
-
|
|
1002
|
-
try {
|
|
1003
|
-
await repoManager.removeTaskWorktree(taskId, { preserveBranch });
|
|
1004
|
-
console.log(`Successfully cleaned up worktree for task ${taskId}`);
|
|
1005
|
-
} catch (error) {
|
|
1006
|
-
console.error(`Failed to clean up worktree for task ${taskId}:`, error);
|
|
1007
|
-
throw error;
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
private async sendGitStateUpdate(taskId: string): Promise<void> {
|
|
1012
|
-
const repoManager = this.runner.repositoryManager_;
|
|
1013
|
-
const gitState = await repoManager.getTaskState(taskId);
|
|
1014
|
-
|
|
1015
|
-
if (!gitState) {
|
|
1016
|
-
console.error(`No Git state found for task ${taskId}`);
|
|
1017
|
-
return;
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
// Send state update to orchestrator
|
|
1021
|
-
await this.runner.notify("git.state.update", {
|
|
1022
|
-
taskId,
|
|
1023
|
-
state: {
|
|
1024
|
-
branch: gitState.branch,
|
|
1025
|
-
commit: gitState.lastCommit || "",
|
|
1026
|
-
isDirty: false, // TODO: Check actual dirty state
|
|
1027
|
-
ahead: 0, // TODO: Calculate ahead/behind
|
|
1028
|
-
behind: 0,
|
|
1029
|
-
},
|
|
1030
|
-
});
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
private async recordGitOperation(
|
|
1034
|
-
taskId: string,
|
|
1035
|
-
operation: string,
|
|
1036
|
-
status: string,
|
|
1037
|
-
details: any
|
|
1038
|
-
): Promise<void> {
|
|
1039
|
-
// Send operation record to orchestrator
|
|
1040
|
-
await this.runner.notify("git.operation.record", {
|
|
1041
|
-
taskId,
|
|
1042
|
-
operation,
|
|
1043
|
-
status,
|
|
1044
|
-
details,
|
|
1045
|
-
timestamp: new Date(),
|
|
1046
|
-
});
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
private resolveAgentProvider(
|
|
1050
|
-
conversation?: {
|
|
1051
|
-
providerType?: string;
|
|
1052
|
-
agentProviderType?: string;
|
|
1053
|
-
model?: string;
|
|
1054
|
-
},
|
|
1055
|
-
config?: ConversationConfig
|
|
1056
|
-
): "openai" | "claude" {
|
|
1057
|
-
const explicitProvider =
|
|
1058
|
-
conversation?.providerType ||
|
|
1059
|
-
conversation?.agentProviderType ||
|
|
1060
|
-
(config as any)?.providerType ||
|
|
1061
|
-
(config as any)?.agentProviderType;
|
|
1062
|
-
|
|
1063
|
-
if (typeof explicitProvider === "string") {
|
|
1064
|
-
const normalized = explicitProvider.toLowerCase();
|
|
1065
|
-
if (normalized === "openai") return "openai";
|
|
1066
|
-
if (normalized === "claude") return "claude";
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
const model =
|
|
1070
|
-
conversation?.model ||
|
|
1071
|
-
(config as any)?.model ||
|
|
1072
|
-
(config as any)?.defaultModel ||
|
|
1073
|
-
"";
|
|
1074
|
-
const normalizedModel = model.toLowerCase();
|
|
1075
|
-
|
|
1076
|
-
const looksLikeCodex =
|
|
1077
|
-
normalizedModel.includes("gpt") ||
|
|
1078
|
-
/^o\d/.test(normalizedModel) ||
|
|
1079
|
-
normalizedModel.includes("openai") ||
|
|
1080
|
-
normalizedModel.includes("codex");
|
|
1081
|
-
|
|
1082
|
-
return looksLikeCodex ? "openai" : "claude";
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
private getManagerForProvider(provider?: string) {
|
|
1086
|
-
if (provider?.toLowerCase() === "openai") {
|
|
1087
|
-
return this.runner.codexManager_;
|
|
1088
|
-
}
|
|
1089
|
-
return this.runner.claudeManager_;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
private getManagerForConversationContext(context: ConversationContext) {
|
|
1093
|
-
return context.provider === "openai"
|
|
1094
|
-
? this.runner.codexManager_
|
|
1095
|
-
: this.runner.claudeManager_;
|
|
1096
|
-
}
|
|
1097
|
-
}
|