@northflare/runner 0.0.8 → 0.0.10

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.
Files changed (101) hide show
  1. package/dist/components/claude-sdk-manager.d.ts +1 -1
  2. package/dist/components/claude-sdk-manager.d.ts.map +1 -1
  3. package/dist/components/claude-sdk-manager.js +30 -16
  4. package/dist/components/claude-sdk-manager.js.map +1 -1
  5. package/dist/components/codex-sdk-manager.d.ts +60 -0
  6. package/dist/components/codex-sdk-manager.d.ts.map +1 -0
  7. package/dist/components/codex-sdk-manager.js +988 -0
  8. package/dist/components/codex-sdk-manager.js.map +1 -0
  9. package/dist/components/message-handler-sse.d.ts +3 -0
  10. package/dist/components/message-handler-sse.d.ts.map +1 -1
  11. package/dist/components/message-handler-sse.js +66 -21
  12. package/dist/components/message-handler-sse.js.map +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +4 -2
  15. package/dist/index.js.map +1 -1
  16. package/dist/runner-sse.d.ts +4 -0
  17. package/dist/runner-sse.d.ts.map +1 -1
  18. package/dist/runner-sse.js +63 -20
  19. package/dist/runner-sse.js.map +1 -1
  20. package/dist/runner.js +3 -3
  21. package/dist/types/claude.d.ts +11 -1
  22. package/dist/types/claude.d.ts.map +1 -1
  23. package/dist/types/index.d.ts +1 -0
  24. package/dist/types/index.d.ts.map +1 -1
  25. package/dist/types/runner-interface.d.ts +2 -0
  26. package/dist/types/runner-interface.d.ts.map +1 -1
  27. package/dist/utils/config.d.ts.map +1 -1
  28. package/dist/utils/config.js +1 -0
  29. package/dist/utils/config.js.map +1 -1
  30. package/dist/utils/console.d.ts.map +1 -1
  31. package/dist/utils/console.js +2 -1
  32. package/dist/utils/console.js.map +1 -1
  33. package/dist/utils/debug.d.ts +2 -0
  34. package/dist/utils/debug.d.ts.map +1 -0
  35. package/dist/utils/debug.js +19 -0
  36. package/dist/utils/debug.js.map +1 -0
  37. package/dist/utils/logger.d.ts.map +1 -1
  38. package/dist/utils/logger.js +6 -4
  39. package/dist/utils/logger.js.map +1 -1
  40. package/dist/utils/model.d.ts +6 -0
  41. package/dist/utils/model.d.ts.map +1 -0
  42. package/dist/utils/model.js +23 -0
  43. package/dist/utils/model.js.map +1 -0
  44. package/dist/utils/status-line.d.ts +0 -8
  45. package/dist/utils/status-line.d.ts.map +1 -1
  46. package/dist/utils/status-line.js +9 -8
  47. package/dist/utils/status-line.js.map +1 -1
  48. package/dist/utils/tool-response-sanitizer.d.ts +9 -0
  49. package/dist/utils/tool-response-sanitizer.d.ts.map +1 -0
  50. package/dist/utils/tool-response-sanitizer.js +122 -0
  51. package/dist/utils/tool-response-sanitizer.js.map +1 -0
  52. package/exceptions.log +2 -0
  53. package/lib/codex-sdk/.prettierignore +3 -0
  54. package/lib/codex-sdk/.prettierrc +5 -0
  55. package/lib/codex-sdk/README.md +133 -0
  56. package/lib/codex-sdk/dist/index.d.ts +260 -0
  57. package/lib/codex-sdk/dist/index.js +426 -0
  58. package/lib/codex-sdk/eslint.config.js +21 -0
  59. package/lib/codex-sdk/jest.config.cjs +31 -0
  60. package/lib/codex-sdk/package.json +65 -0
  61. package/lib/codex-sdk/samples/basic_streaming.ts +90 -0
  62. package/lib/codex-sdk/samples/helpers.ts +8 -0
  63. package/lib/codex-sdk/samples/structured_output.ts +22 -0
  64. package/lib/codex-sdk/samples/structured_output_zod.ts +19 -0
  65. package/lib/codex-sdk/src/codex.ts +38 -0
  66. package/lib/codex-sdk/src/codexOptions.ts +10 -0
  67. package/lib/codex-sdk/src/events.ts +80 -0
  68. package/lib/codex-sdk/src/exec.ts +336 -0
  69. package/lib/codex-sdk/src/index.ts +39 -0
  70. package/lib/codex-sdk/src/items.ts +127 -0
  71. package/lib/codex-sdk/src/outputSchemaFile.ts +40 -0
  72. package/lib/codex-sdk/src/thread.ts +155 -0
  73. package/lib/codex-sdk/src/threadOptions.ts +18 -0
  74. package/lib/codex-sdk/src/turnOptions.ts +6 -0
  75. package/lib/codex-sdk/tests/abort.test.ts +165 -0
  76. package/lib/codex-sdk/tests/codexExecSpy.ts +37 -0
  77. package/lib/codex-sdk/tests/responsesProxy.ts +225 -0
  78. package/lib/codex-sdk/tests/run.test.ts +687 -0
  79. package/lib/codex-sdk/tests/runStreamed.test.ts +211 -0
  80. package/lib/codex-sdk/tsconfig.json +24 -0
  81. package/lib/codex-sdk/tsup.config.ts +12 -0
  82. package/package.json +3 -1
  83. package/rejections.log +5 -0
  84. package/src/components/claude-sdk-manager.ts +38 -17
  85. package/src/components/codex-sdk-manager.ts +1349 -0
  86. package/src/components/message-handler-sse.ts +94 -22
  87. package/src/index.ts +4 -2
  88. package/src/runner-sse.ts +75 -20
  89. package/src/types/claude.ts +12 -1
  90. package/src/types/index.ts +1 -0
  91. package/src/types/runner-interface.ts +3 -1
  92. package/src/utils/config.ts +1 -0
  93. package/src/utils/console.ts +4 -2
  94. package/src/utils/debug.ts +18 -0
  95. package/src/utils/logger.ts +8 -5
  96. package/src/utils/model.ts +29 -0
  97. package/src/utils/status-line.ts +9 -8
  98. package/src/utils/tool-response-sanitizer.ts +160 -0
  99. package/tests/tool-response-sanitizer.test.ts +63 -0
  100. package/src/utils/codex-sdk.js +0 -448
  101. package/src/utils/sdk-demo.js +0 -34
@@ -3,12 +3,18 @@
3
3
  */
4
4
 
5
5
  import { IRunnerApp } from "../types/runner-interface";
6
- import { RunnerMessage, MethodHandler, ConversationContext } from "../types";
6
+ import {
7
+ RunnerMessage,
8
+ MethodHandler,
9
+ ConversationContext,
10
+ ConversationConfig,
11
+ } from "../types";
7
12
  import { SSEClient, SSEEvent } from "../services/SSEClient";
8
13
  import { RunnerAPIClient } from "../services/RunnerAPIClient";
9
14
  import { statusLineManager } from "../utils/status-line";
10
15
  import { console } from "../utils/console";
11
16
  import { createLogger } from "../utils/logger";
17
+ import { isRunnerDebugEnabled } from "../utils/debug";
12
18
 
13
19
  const logger = createLogger("MessageHandler");
14
20
 
@@ -151,7 +157,7 @@ export class MessageHandler {
151
157
  if (event.type === "runner.message") {
152
158
  const message = event.data as RunnerMessage;
153
159
 
154
- if (process.env["DEBUG"] === "true") {
160
+ if (isRunnerDebugEnabled()) {
155
161
  logger.debug("Received SSE event", {
156
162
  eventId: event.id,
157
163
  type: event.type,
@@ -178,7 +184,7 @@ export class MessageHandler {
178
184
  }
179
185
 
180
186
  private async processMessage(message: RunnerMessage): Promise<void> {
181
- if (process.env["DEBUG"] === "true") {
187
+ if (isRunnerDebugEnabled()) {
182
188
  logger.debug("processMessage called", {
183
189
  messageId: message.id,
184
190
  method: message.payload?.method,
@@ -211,7 +217,7 @@ export class MessageHandler {
211
217
  return;
212
218
  }
213
219
 
214
- if (process.env["DEBUG"] === "true") {
220
+ if (isRunnerDebugEnabled()) {
215
221
  logger.debug("Processing message", {
216
222
  messageId: message.id,
217
223
  method: method,
@@ -227,7 +233,7 @@ export class MessageHandler {
227
233
  // Acknowledge ALL messages to update lastProcessedAt
228
234
  await this.acknowledgeMessage(message);
229
235
 
230
- if (process.env["DEBUG"] === "true") {
236
+ if (isRunnerDebugEnabled()) {
231
237
  logger.debug("Message acknowledged", {
232
238
  messageId: message.id,
233
239
  method: method,
@@ -236,7 +242,7 @@ export class MessageHandler {
236
242
  });
237
243
  }
238
244
  } catch (error) {
239
- if (process.env["DEBUG"] === "true") {
245
+ if (isRunnerDebugEnabled()) {
240
246
  logger.debug("Message processing error", {
241
247
  messageId: message.id,
242
248
  method: method,
@@ -293,7 +299,7 @@ export class MessageHandler {
293
299
  };
294
300
  })();
295
301
 
296
- if (process.env["DEBUG"] === "true") {
302
+ if (isRunnerDebugEnabled()) {
297
303
  logger.debug("Message processing decision", {
298
304
  messageId: message.id,
299
305
  method: message.payload?.method,
@@ -361,7 +367,7 @@ export class MessageHandler {
361
367
  console.error(
362
368
  `[MessageHandler] Cannot send error report - no conversationObjectId available. Error: ${errorMessage}`
363
369
  );
364
- if (process.env["DEBUG"] === "true") {
370
+ if (isRunnerDebugEnabled()) {
365
371
  logger.debug("Error without conversationObjectId", {
366
372
  messageId: message.id,
367
373
  method: message.payload?.method,
@@ -398,7 +404,7 @@ export class MessageHandler {
398
404
  console.error(
399
405
  `[MessageHandler] Cannot send error report - no conversationObjectId available. Processing error: ${errorMessage}`
400
406
  );
401
- if (process.env["DEBUG"] === "true") {
407
+ if (isRunnerDebugEnabled()) {
402
408
  logger.debug("Processing error without conversationObjectId", {
403
409
  messageId: message.id,
404
410
  method: message.payload?.method,
@@ -499,8 +505,11 @@ export class MessageHandler {
499
505
  }
500
506
  );
501
507
 
508
+ const provider = this.resolveAgentProvider(conversationData, config);
509
+ const manager = this.getManagerForProvider(provider);
510
+
502
511
  // Start the conversation with the provided/loaded conversation details
503
- await this.runner.claudeManager_.startConversation(
512
+ await manager.startConversation(
504
513
  finalObjectType,
505
514
  finalObjectId,
506
515
  config,
@@ -551,7 +560,8 @@ export class MessageHandler {
551
560
 
552
561
  if (context && targetConversationId) {
553
562
  context.status = "stopping";
554
- await this.runner.claudeManager_.stopConversation(
563
+ const manager = this.getManagerForConversationContext(context);
564
+ await manager.stopConversation(
555
565
  context.agentSessionId,
556
566
  context,
557
567
  false, // Not a runner shutdown
@@ -611,7 +621,9 @@ export class MessageHandler {
611
621
  throw new Error("Cannot resume conversation without agentSessionId");
612
622
  }
613
623
 
614
- await this.runner.claudeManager_.resumeConversation(
624
+ const provider = this.resolveAgentProvider(conversationData, config);
625
+ const manager = this.getManagerForProvider(provider);
626
+ await manager.resumeConversation(
615
627
  conversationData.objectType,
616
628
  conversationData.objectId,
617
629
  agentSessionId,
@@ -666,7 +678,9 @@ export class MessageHandler {
666
678
  );
667
679
 
668
680
  // Stop the current conversation
669
- await this.runner.claudeManager_.stopConversation(
681
+ const manager = this.getManagerForConversationContext(context);
682
+
683
+ await manager.stopConversation(
670
684
  context.agentSessionId,
671
685
  context,
672
686
  false // Not a runner shutdown, just updating config
@@ -680,7 +694,7 @@ export class MessageHandler {
680
694
  );
681
695
 
682
696
  // Resume with new config
683
- await this.runner.claudeManager_.resumeConversation(
697
+ await manager.resumeConversation(
684
698
  context.conversationObjectType,
685
699
  context.conversationObjectId,
686
700
  context.agentSessionId,
@@ -717,6 +731,7 @@ export class MessageHandler {
717
731
  conversationObjectType = message.conversationObjectType || "Task",
718
732
  conversationObjectId = message.conversationObjectId,
719
733
  conversation,
734
+ agentSessionId,
720
735
  } = params;
721
736
 
722
737
  // Validate required parameters
@@ -728,13 +743,21 @@ export class MessageHandler {
728
743
  throw new Error("Missing required parameter: content");
729
744
  }
730
745
 
731
- await this.runner.claudeManager_.sendUserMessage(
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(
732
754
  conversationId,
733
755
  content,
734
756
  config,
735
757
  conversationObjectType,
736
758
  conversationObjectId,
737
- conversation
759
+ conversation,
760
+ agentSessionId
738
761
  );
739
762
  }
740
763
 
@@ -748,7 +771,7 @@ export class MessageHandler {
748
771
  `MessageHandler: Handling UID change notification - new UID: ${runnerUid}, lastProcessedAt: ${lastProcessedAt}`
749
772
  );
750
773
 
751
- if (process.env["DEBUG"] === "true") {
774
+ if (isRunnerDebugEnabled()) {
752
775
  logger.debug("UID change notification received", {
753
776
  newUid: runnerUid,
754
777
  currentUid: this.runner.getRunnerUid(),
@@ -770,7 +793,7 @@ export class MessageHandler {
770
793
  lastProcessedAt ? new Date(lastProcessedAt) : null
771
794
  );
772
795
 
773
- if (process.env["DEBUG"] === "true") {
796
+ if (isRunnerDebugEnabled()) {
774
797
  logger.debug("Runner activated as primary", {
775
798
  runnerUid: runnerUid,
776
799
  lastProcessedAt: lastProcessedAt,
@@ -786,7 +809,7 @@ export class MessageHandler {
786
809
  });
787
810
  } catch (error) {
788
811
  console.error("Failed to send activation notification:", error);
789
- if (process.env["DEBUG"] === "true") {
812
+ if (isRunnerDebugEnabled()) {
790
813
  logger.debug("Activation notification failed", {
791
814
  error: error instanceof Error ? error.message : String(error),
792
815
  });
@@ -808,7 +831,7 @@ export class MessageHandler {
808
831
  console.log(
809
832
  `MessageHandler: Ignoring old UID change (${lastProcessedAt} < ${currentLastProcessedAt.toISOString()})`
810
833
  );
811
- if (process.env["DEBUG"] === "true") {
834
+ if (isRunnerDebugEnabled()) {
812
835
  logger.debug("Ignoring old UID change", {
813
836
  newUid: runnerUid,
814
837
  newLastProcessedAt: lastProcessedAt,
@@ -833,7 +856,7 @@ export class MessageHandler {
833
856
  }
834
857
  }
835
858
 
836
- if (process.env["DEBUG"] === "true") {
859
+ if (isRunnerDebugEnabled()) {
837
860
  logger.debug("Runner deactivated - being replaced", {
838
861
  newRunnerUid: runnerUid,
839
862
  ourUid: this.runner.getRunnerUid(),
@@ -860,7 +883,7 @@ export class MessageHandler {
860
883
  });
861
884
  } catch (error) {
862
885
  console.error("Failed to send deactivation notification:", error);
863
- if (process.env["DEBUG"] === "true") {
886
+ if (isRunnerDebugEnabled()) {
864
887
  logger.debug("Deactivation notification failed", {
865
888
  error: error instanceof Error ? error.message : String(error),
866
889
  });
@@ -1022,4 +1045,53 @@ export class MessageHandler {
1022
1045
  timestamp: new Date(),
1023
1046
  });
1024
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
+ }
1025
1097
  }
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  import { RunnerApp } from "./runner-sse";
6
6
  import { ConfigManager } from "./utils/config";
7
7
  import { logger, configureFileLogging } from "./utils/logger";
8
+ import { isRunnerDebugEnabled } from "./utils/debug";
8
9
  import path from "path";
9
10
  import fs from "fs/promises";
10
11
 
@@ -13,6 +14,7 @@ let runner: RunnerApp | null = null;
13
14
  async function main() {
14
15
  try {
15
16
  logger.info("Starting Northflare Runner...");
17
+ const debugEnabled = isRunnerDebugEnabled();
16
18
 
17
19
  // Load configuration (args already parsed by CLI)
18
20
  let configPath = process.argv[2]; // This is set by the CLI if --config was provided
@@ -53,7 +55,7 @@ async function main() {
53
55
  });
54
56
 
55
57
  // Additional debug logging
56
- if (process.env["DEBUG"] === "true") {
58
+ if (debugEnabled) {
57
59
  logger.debug("Debug mode enabled - verbose logging active", {
58
60
  dataDir: config.dataDir,
59
61
  heartbeatInterval: config.heartbeatInterval,
@@ -75,7 +77,7 @@ async function main() {
75
77
  });
76
78
 
77
79
  // Log additional details in debug mode
78
- if (process.env["DEBUG"] === "true" && runner) {
80
+ if (debugEnabled && runner) {
79
81
  logger.debug("Runner started with full details", {
80
82
  runnerId: runner.getRunnerId(),
81
83
  runnerUid: runner.getRunnerUid(),
package/src/runner-sse.ts CHANGED
@@ -13,19 +13,35 @@ import {
13
13
  import { IRunnerApp } from "./types/runner-interface";
14
14
  import { MessageHandler } from "./components/message-handler-sse";
15
15
  import { ClaudeManager } from "./components/claude-sdk-manager";
16
+ import { CodexManager } from "./components/codex-sdk-manager";
16
17
  import { EnhancedRepositoryManager } from "./components/enhanced-repository-manager";
17
18
  import { StateManager } from "./utils/StateManager";
18
19
  import { createLogger } from "./utils/logger";
19
20
  import { statusLineManager } from "./utils/status-line";
21
+ import { isRunnerDebugEnabled } from "./utils/debug";
20
22
  import fs from "fs/promises";
21
23
  import path from "path";
22
24
  import computerName from "computer-name";
25
+ import {
26
+ sanitizeToolResponsePayload,
27
+ TOOL_RESPONSE_BYTE_LIMIT,
28
+ } from "./utils/tool-response-sanitizer";
23
29
 
24
30
  const logger = createLogger("RunnerApp");
25
31
 
32
+ function logToolResponseTruncation(toolUseId?: string) {
33
+ const message = `Tool response exceeded ${TOOL_RESPONSE_BYTE_LIMIT} bytes and was truncated`;
34
+ if (toolUseId) {
35
+ logger.warn(message, { toolUseId });
36
+ } else {
37
+ logger.warn(message);
38
+ }
39
+ }
40
+
26
41
  export class RunnerApp implements IRunnerApp {
27
42
  private messageHandler!: MessageHandler;
28
43
  private claudeManager!: ClaudeManager;
44
+ private codexManager!: CodexManager;
29
45
  private repositoryManager!: EnhancedRepositoryManager;
30
46
  private stateManager!: StateManager;
31
47
  private agentConversations: Map<string, ConversationContext>; // Keyed by conversation.id
@@ -91,6 +107,9 @@ export class RunnerApp implements IRunnerApp {
91
107
  // Initialize Claude manager with repository manager (using SDK-native manager)
92
108
  this.claudeManager = new ClaudeManager(this, this.repositoryManager);
93
109
 
110
+ // Initialize Codex manager for OpenAI-based conversations
111
+ this.codexManager = new CodexManager(this, this.repositoryManager);
112
+
94
113
  // Initialize message handler with SSE support
95
114
  this.messageHandler = new MessageHandler(this);
96
115
  }
@@ -139,7 +158,7 @@ export class RunnerApp implements IRunnerApp {
139
158
  await this.registerWithRetry();
140
159
 
141
160
  // Log debug info after registration
142
- if (process.env["DEBUG"] === "true") {
161
+ if (isRunnerDebugEnabled()) {
143
162
  logger.debug("Runner initialized with ownership details", {
144
163
  runnerId: this.config.runnerId,
145
164
  runnerUid: this.runnerUid,
@@ -193,17 +212,24 @@ export class RunnerApp implements IRunnerApp {
193
212
 
194
213
  async notify(method: string, params: any): Promise<void> {
195
214
  try {
215
+ const sanitization = sanitizeToolResponsePayload(method, params);
216
+ if (sanitization.truncated) {
217
+ logToolResponseTruncation(sanitization.toolUseId);
218
+ }
219
+ const safeParams = sanitization.truncated
220
+ ? sanitization.params
221
+ : params;
196
222
  // Log RPC notification in debug mode
197
223
  logger.debug(`[RPC] Sending notification: ${method}`, {
198
224
  method,
199
- params: JSON.stringify(params, null, 2),
225
+ params: JSON.stringify(safeParams, null, 2),
200
226
  });
201
227
 
202
228
  // Send notification with retry logic
203
229
  await this.sendToOrchestratorWithRetry({
204
230
  jsonrpc: "2.0",
205
231
  method,
206
- params,
232
+ params: safeParams,
207
233
  });
208
234
  } catch (error) {
209
235
  // Special handling for heartbeat errors - just log a simple line
@@ -224,13 +250,28 @@ export class RunnerApp implements IRunnerApp {
224
250
  }
225
251
 
226
252
  async sendToOrchestrator(message: JsonRpcMessage): Promise<any> {
253
+ const sanitization = sanitizeToolResponsePayload(
254
+ message.method,
255
+ message.params
256
+ );
257
+ if (sanitization.truncated) {
258
+ logToolResponseTruncation(sanitization.toolUseId);
259
+ }
260
+
261
+ const messageToSend = sanitization.truncated
262
+ ? {
263
+ ...message,
264
+ params: sanitization.params,
265
+ }
266
+ : message;
267
+
227
268
  try {
228
269
  // Log RPC request in debug mode
229
270
  logger.debug(`[RPC] Sending request:`, {
230
- method: message.method,
231
- id: message.id,
232
- params: message.params
233
- ? JSON.stringify(message.params, null, 2)
271
+ method: messageToSend.method,
272
+ id: messageToSend.id,
273
+ params: messageToSend.params
274
+ ? JSON.stringify(messageToSend.params, null, 2)
234
275
  : undefined,
235
276
  });
236
277
 
@@ -250,7 +291,7 @@ export class RunnerApp implements IRunnerApp {
250
291
  {
251
292
  method: "POST",
252
293
  headers,
253
- body: JSON.stringify(message),
294
+ body: JSON.stringify(messageToSend),
254
295
  signal: AbortSignal.timeout(30000), // 30 second timeout
255
296
  }
256
297
  );
@@ -264,8 +305,8 @@ export class RunnerApp implements IRunnerApp {
264
305
 
265
306
  // Log RPC response in debug mode
266
307
  logger.debug(`[RPC] Received response:`, {
267
- method: message.method,
268
- id: message.id,
308
+ method: messageToSend.method,
309
+ id: messageToSend.id,
269
310
  result: result?.result
270
311
  ? JSON.stringify(result.result, null, 2)
271
312
  : undefined,
@@ -288,8 +329,8 @@ export class RunnerApp implements IRunnerApp {
288
329
  } else {
289
330
  // For other RPC messages, log the attempted message and error
290
331
  logger.error(`RPC failed:`, {
291
- method: message.method,
292
- params: message.params,
332
+ method: messageToSend.method,
333
+ params: messageToSend.params,
293
334
  error: errorMessage,
294
335
  });
295
336
  }
@@ -302,7 +343,7 @@ export class RunnerApp implements IRunnerApp {
302
343
  ): ConversationContext | undefined {
303
344
  // Using conversation.id as primary key for conversation tracking
304
345
  const context = this.agentConversations.get(conversationId);
305
- console.log(`[Runner] getConversationContext lookup:`, {
346
+ logger.debug(`[Runner] getConversationContext lookup:`, {
306
347
  conversationId,
307
348
  found: !!context,
308
349
  totalConversations: this.agentConversations.size,
@@ -327,12 +368,12 @@ export class RunnerApp implements IRunnerApp {
327
368
  `RunnerRepo validated: ${repo.name} at ${resolvedPath}`
328
369
  );
329
370
  } else {
330
- logger.error(
371
+ logger.warn(
331
372
  `RunnerRepo path is not a directory: ${repo.name} at ${resolvedPath}`
332
373
  );
333
374
  }
334
375
  } catch (error) {
335
- logger.error(
376
+ logger.warn(
336
377
  `RunnerRepo path does not exist: ${repo.name} at ${repo.path}`
337
378
  );
338
379
  }
@@ -362,7 +403,7 @@ export class RunnerApp implements IRunnerApp {
362
403
  logger.info(`Registration response:`, JSON.stringify(response, null, 2));
363
404
 
364
405
  // Log the runnerRepos being sent and received
365
- if (process.env["DEBUG"] === "true") {
406
+ if (isRunnerDebugEnabled()) {
366
407
  logger.debug(
367
408
  "Registration runnerRepos sent:",
368
409
  JSON.stringify(filteredRunnerRepos)
@@ -424,7 +465,7 @@ export class RunnerApp implements IRunnerApp {
424
465
  }
425
466
 
426
467
  // Debug logging for registration details
427
- if (process.env["DEBUG"] === "true") {
468
+ if (isRunnerDebugEnabled()) {
428
469
  logger.debug("Registration complete with details", {
429
470
  runnerId: this.config.runnerId,
430
471
  runnerUid: this.runnerUid,
@@ -760,8 +801,9 @@ export class RunnerApp implements IRunnerApp {
760
801
 
761
802
  for (const [conversationId, context] of this.agentConversations) {
762
803
  if (context.status === "active" || context.status === "starting") {
804
+ const manager = this.getManagerForContext(context);
763
805
  stopPromises.push(
764
- this.claudeManager
806
+ manager
765
807
  .stopConversation(context.agentSessionId, context, isRunnerShutdown)
766
808
  .catch((error) => {
767
809
  logger.error(
@@ -797,6 +839,9 @@ export class RunnerApp implements IRunnerApp {
797
839
  get claudeManager_(): ClaudeManager {
798
840
  return this.claudeManager;
799
841
  }
842
+ get codexManager_(): CodexManager {
843
+ return this.codexManager;
844
+ }
800
845
  get repositoryManager_(): EnhancedRepositoryManager {
801
846
  return this.repositoryManager;
802
847
  }
@@ -827,7 +872,7 @@ export class RunnerApp implements IRunnerApp {
827
872
  logger.error("Failed to persist active status:", error);
828
873
  });
829
874
 
830
- if (process.env["DEBUG"] === "true") {
875
+ if (isRunnerDebugEnabled()) {
831
876
  logger.debug("Active runner status changed", {
832
877
  previous: previousState,
833
878
  new: active,
@@ -841,7 +886,7 @@ export class RunnerApp implements IRunnerApp {
841
886
  const previousTimestamp = this.lastProcessedAt;
842
887
  this.lastProcessedAt = timestamp;
843
888
 
844
- if (process.env["DEBUG"] === "true") {
889
+ if (isRunnerDebugEnabled()) {
845
890
  logger.debug("LastProcessedAt updated", {
846
891
  previous: previousTimestamp?.toISOString() || "null",
847
892
  new: timestamp?.toISOString() || "null",
@@ -859,4 +904,14 @@ export class RunnerApp implements IRunnerApp {
859
904
  setDelayFunction(fn: (ms: number) => Promise<void>): void {
860
905
  this.delayFn = fn;
861
906
  }
907
+
908
+ private getManagerForContext(
909
+ context?: ConversationContext | null
910
+ ): ClaudeManager | CodexManager {
911
+ const provider = context?.provider?.toLowerCase();
912
+ if (provider === "openai") {
913
+ return this.codexManager;
914
+ }
915
+ return this.claudeManager;
916
+ }
862
917
  }
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { Conversation } from "@botanicastudios/claude-code-sdk-ts";
6
+ import type { Thread } from "@northflare/codex-sdk";
6
7
 
7
8
  // Repository information passed in conversation config
8
9
  export interface RepositoryInfo {
@@ -25,6 +26,7 @@ export interface ConversationConfig {
25
26
  repository?: RepositoryInfo; // Repository details from orchestrator
26
27
  sessionId?: string; // For resume operations
27
28
  runnerRepoPath?: string; // Path to local runner repo for local workspaces
29
+ codexAuth?: CodexAuthConfig;
28
30
  }
29
31
 
30
32
  // Internal conversation tracking
@@ -39,7 +41,9 @@ export interface ConversationContext {
39
41
  config: ConversationConfig;
40
42
  startedAt: Date;
41
43
  lastActivityAt: Date;
42
- conversation?: Conversation; // Claude SDK conversation instance (stateful)
44
+ conversation?: Conversation | Thread; // Conversation or Codex thread instance
45
+ provider?: "claude" | "openai" | string;
46
+ metadata?: Record<string, any>;
43
47
 
44
48
  // Additional conversation details from database
45
49
  model: string;
@@ -48,6 +52,13 @@ export interface ConversationContext {
48
52
  permissionsMode: string;
49
53
  }
50
54
 
55
+ export interface CodexAuthConfig {
56
+ accessToken: string;
57
+ idToken: string;
58
+ accountId: string;
59
+ lastRefresh?: string | null;
60
+ }
61
+
51
62
  // Message type for initial messages
52
63
  export interface Message {
53
64
  content: string;
@@ -59,5 +59,6 @@ export interface EnvironmentConfig {
59
59
  NORTHFLARE_RUNNER_TOKEN: string;
60
60
  NORTHFLARE_WORKSPACE_DIR: string;
61
61
  NORTHFLARE_ORCHESTRATOR_URL: string;
62
+ NORTHFLARE_RUNNER_DEBUG?: string;
62
63
  DEBUG?: string;
63
64
  }
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { RunnerConfig, ConversationContext, JsonRpcMessage } from './index';
6
6
  import { ClaudeManager } from '../components/claude-sdk-manager';
7
+ import { CodexManager } from '../components/codex-sdk-manager';
7
8
  import { EnhancedRepositoryManager } from '../components/enhanced-repository-manager';
8
9
 
9
10
  export interface IRunnerApp {
@@ -18,6 +19,7 @@ export interface IRunnerApp {
18
19
  get config_(): RunnerConfig;
19
20
  get activeConversations_(): Map<string, ConversationContext>;
20
21
  get claudeManager_(): ClaudeManager;
22
+ get codexManager_(): CodexManager;
21
23
  get repositoryManager_(): EnhancedRepositoryManager;
22
24
 
23
25
  // Runner state
@@ -31,4 +33,4 @@ export interface IRunnerApp {
31
33
 
32
34
  // Optional methods for SSE implementation
33
35
  updateLastProcessedAt?(timestamp: Date | null): Promise<void>;
34
- }
36
+ }
@@ -294,6 +294,7 @@ export class ConfigManager {
294
294
  NORTHFLARE_RUNNER_TOKEN: process.env["NORTHFLARE_RUNNER_TOKEN"]!,
295
295
  NORTHFLARE_WORKSPACE_DIR: process.env["NORTHFLARE_WORKSPACE_DIR"]!,
296
296
  NORTHFLARE_ORCHESTRATOR_URL: process.env["NORTHFLARE_ORCHESTRATOR_URL"]!,
297
+ NORTHFLARE_RUNNER_DEBUG: process.env["NORTHFLARE_RUNNER_DEBUG"],
297
298
  DEBUG: process.env["DEBUG"],
298
299
  };
299
300
  }
@@ -2,7 +2,9 @@
2
2
  * Console wrapper that suppresses output when not in debug mode
3
3
  */
4
4
 
5
- const isDebug = process.env["DEBUG"] === "true";
5
+ import { isRunnerDebugEnabled } from "./debug";
6
+
7
+ const isDebug = isRunnerDebugEnabled();
6
8
 
7
9
  export const console = {
8
10
  log: isDebug ? global.console.log.bind(global.console) : () => {},
@@ -10,4 +12,4 @@ export const console = {
10
12
  error: global.console.error.bind(global.console), // Always show errors
11
13
  info: isDebug ? global.console.info.bind(global.console) : () => {},
12
14
  debug: isDebug ? global.console.debug.bind(global.console) : () => {},
13
- };
15
+ };
@@ -0,0 +1,18 @@
1
+ export function isRunnerDebugEnabled(): boolean {
2
+ const explicitFlag = process.env["NORTHFLARE_RUNNER_DEBUG"];
3
+ if (typeof explicitFlag === "string") {
4
+ return isTruthy(explicitFlag);
5
+ }
6
+
7
+ const legacyFlag = process.env["DEBUG"];
8
+ if (typeof legacyFlag === "string") {
9
+ return isTruthy(legacyFlag);
10
+ }
11
+
12
+ return false;
13
+ }
14
+
15
+ function isTruthy(value: string): boolean {
16
+ const normalized = value.trim().toLowerCase();
17
+ return normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on";
18
+ }