@northflare/runner 0.0.13 → 0.0.16

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 (119) hide show
  1. package/README.md +8 -5
  2. package/bin/northflare-runner +44 -16
  3. package/dist/components/claude-sdk-manager.d.ts +3 -3
  4. package/dist/components/claude-sdk-manager.d.ts.map +1 -1
  5. package/dist/components/claude-sdk-manager.js +80 -111
  6. package/dist/components/claude-sdk-manager.js.map +1 -1
  7. package/dist/components/codex-sdk-manager.d.ts +15 -6
  8. package/dist/components/codex-sdk-manager.d.ts.map +1 -1
  9. package/dist/components/codex-sdk-manager.js +128 -97
  10. package/dist/components/codex-sdk-manager.js.map +1 -1
  11. package/dist/components/enhanced-repository-manager.d.ts +2 -2
  12. package/dist/components/enhanced-repository-manager.d.ts.map +1 -1
  13. package/dist/components/enhanced-repository-manager.js +68 -75
  14. package/dist/components/enhanced-repository-manager.js.map +1 -1
  15. package/dist/components/message-handler-sse.d.ts +1 -1
  16. package/dist/components/message-handler-sse.d.ts.map +1 -1
  17. package/dist/components/message-handler-sse.js +205 -97
  18. package/dist/components/message-handler-sse.js.map +1 -1
  19. package/dist/components/{claude-manager.d.ts → northflare-agent-sdk-manager.d.ts} +17 -14
  20. package/dist/components/northflare-agent-sdk-manager.d.ts.map +1 -0
  21. package/dist/components/northflare-agent-sdk-manager.js +1456 -0
  22. package/dist/components/northflare-agent-sdk-manager.js.map +1 -0
  23. package/dist/components/repository-manager.d.ts +2 -2
  24. package/dist/components/repository-manager.d.ts.map +1 -1
  25. package/dist/components/repository-manager.js +34 -74
  26. package/dist/components/repository-manager.js.map +1 -1
  27. package/dist/index.d.ts +3 -3
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +115 -79
  30. package/dist/index.js.map +1 -1
  31. package/dist/runner-sse.d.ts +22 -5
  32. package/dist/runner-sse.d.ts.map +1 -1
  33. package/dist/runner-sse.js +95 -74
  34. package/dist/runner-sse.js.map +1 -1
  35. package/dist/services/RunnerAPIClient.d.ts +1 -1
  36. package/dist/services/RunnerAPIClient.d.ts.map +1 -1
  37. package/dist/services/RunnerAPIClient.js +3 -7
  38. package/dist/services/RunnerAPIClient.js.map +1 -1
  39. package/dist/services/SSEClient.js +5 -9
  40. package/dist/services/SSEClient.js.map +1 -1
  41. package/dist/types/claude.d.ts +16 -2
  42. package/dist/types/claude.d.ts.map +1 -1
  43. package/dist/types/claude.js +1 -2
  44. package/dist/types/claude.js.map +1 -1
  45. package/dist/types/index.d.ts +5 -3
  46. package/dist/types/index.d.ts.map +1 -1
  47. package/dist/types/index.js +3 -19
  48. package/dist/types/index.js.map +1 -1
  49. package/dist/types/messages.js +1 -2
  50. package/dist/types/messages.js.map +1 -1
  51. package/dist/types/runner-interface.d.ts +8 -4
  52. package/dist/types/runner-interface.d.ts.map +1 -1
  53. package/dist/types/runner-interface.js +1 -2
  54. package/dist/types/runner-interface.js.map +1 -1
  55. package/dist/utils/StateManager.js +12 -19
  56. package/dist/utils/StateManager.js.map +1 -1
  57. package/dist/utils/config.d.ts +1 -1
  58. package/dist/utils/config.d.ts.map +1 -1
  59. package/dist/utils/config.js +19 -26
  60. package/dist/utils/config.js.map +1 -1
  61. package/dist/utils/console.js +3 -6
  62. package/dist/utils/console.js.map +1 -1
  63. package/dist/utils/debug.js +1 -4
  64. package/dist/utils/debug.js.map +1 -1
  65. package/dist/utils/expand-env.js +1 -4
  66. package/dist/utils/expand-env.js.map +1 -1
  67. package/dist/utils/inactivity-timeout.d.ts +19 -0
  68. package/dist/utils/inactivity-timeout.d.ts.map +1 -0
  69. package/dist/utils/inactivity-timeout.js +72 -0
  70. package/dist/utils/inactivity-timeout.js.map +1 -0
  71. package/dist/utils/logger.d.ts.map +1 -1
  72. package/dist/utils/logger.js +24 -35
  73. package/dist/utils/logger.js.map +1 -1
  74. package/dist/utils/model.d.ts +3 -1
  75. package/dist/utils/model.d.ts.map +1 -1
  76. package/dist/utils/model.js +18 -4
  77. package/dist/utils/model.js.map +1 -1
  78. package/dist/utils/status-line.d.ts +1 -0
  79. package/dist/utils/status-line.d.ts.map +1 -1
  80. package/dist/utils/status-line.js +25 -18
  81. package/dist/utils/status-line.js.map +1 -1
  82. package/dist/utils/tool-response-sanitizer.js +6 -10
  83. package/dist/utils/tool-response-sanitizer.js.map +1 -1
  84. package/lib/codex-sdk/dist/index.d.ts +1 -1
  85. package/lib/codex-sdk/dist/samples/basic_streaming.d.ts +3 -0
  86. package/lib/codex-sdk/dist/samples/basic_streaming.d.ts.map +1 -0
  87. package/lib/codex-sdk/dist/samples/basic_streaming.js +81 -0
  88. package/lib/codex-sdk/dist/samples/basic_streaming.js.map +1 -0
  89. package/lib/codex-sdk/dist/samples/helpers.d.ts +2 -0
  90. package/lib/codex-sdk/dist/samples/helpers.d.ts.map +1 -0
  91. package/lib/codex-sdk/dist/samples/helpers.js +6 -0
  92. package/lib/codex-sdk/dist/samples/helpers.js.map +1 -0
  93. package/lib/codex-sdk/dist/samples/structured_output.d.ts +3 -0
  94. package/lib/codex-sdk/dist/samples/structured_output.d.ts.map +1 -0
  95. package/lib/codex-sdk/dist/samples/structured_output.js +17 -0
  96. package/lib/codex-sdk/dist/samples/structured_output.js.map +1 -0
  97. package/lib/codex-sdk/dist/samples/structured_output_zod.d.ts +3 -0
  98. package/lib/codex-sdk/dist/samples/structured_output_zod.d.ts.map +1 -0
  99. package/lib/codex-sdk/dist/samples/structured_output_zod.js +16 -0
  100. package/lib/codex-sdk/dist/samples/structured_output_zod.js.map +1 -0
  101. package/lib/codex-sdk/dist/tsup.config.d.ts +3 -0
  102. package/lib/codex-sdk/dist/tsup.config.js +12 -0
  103. package/package.json +9 -4
  104. package/scripts/verify-openrouter-agent.ts +163 -0
  105. package/dist/collections/runner-messages.d.ts +0 -52
  106. package/dist/collections/runner-messages.d.ts.map +0 -1
  107. package/dist/collections/runner-messages.js +0 -161
  108. package/dist/collections/runner-messages.js.map +0 -1
  109. package/dist/components/claude-manager.d.ts.map +0 -1
  110. package/dist/components/claude-manager.js +0 -783
  111. package/dist/components/claude-manager.js.map +0 -1
  112. package/dist/components/message-handler.d.ts +0 -35
  113. package/dist/components/message-handler.d.ts.map +0 -1
  114. package/dist/components/message-handler.js +0 -689
  115. package/dist/components/message-handler.js.map +0 -1
  116. package/dist/runner.d.ts +0 -51
  117. package/dist/runner.d.ts.map +0 -1
  118. package/dist/runner.js +0 -530
  119. package/dist/runner.js.map +0 -1
@@ -1,783 +0,0 @@
1
- "use strict";
2
- /**
3
- * ClaudeManager - Manages Claude conversations using claude-code-sdk-ts
4
- *
5
- * This component handles stateful conversation lifecycle management, maintaining
6
- * persistent conversation instances indexed by taskId. It integrates with the
7
- * Claude SDK to manage sessions, handle MCP server configurations, and coordinate
8
- * with the orchestrator for authentication and notifications.
9
- */
10
- var __importDefault = (this && this.__importDefault) || function (mod) {
11
- return (mod && mod.__esModule) ? mod : { "default": mod };
12
- };
13
- Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.ClaudeManager = void 0;
15
- const claude_code_sdk_ts_1 = require("@botanicastudios/claude-code-sdk-ts");
16
- const status_line_1 = require("../utils/status-line");
17
- const console_1 = require("../utils/console");
18
- const expand_env_1 = require("../utils/expand-env");
19
- const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
20
- class ClaudeManager {
21
- runner;
22
- repositoryManager;
23
- constructor(runner, repositoryManager) {
24
- this.runner = runner;
25
- this.repositoryManager = repositoryManager;
26
- // Log debug mode status
27
- if (process.env["DEBUG"] === "true") {
28
- console_1.console.log("[ClaudeManager] DEBUG MODE ENABLED - Claude SDK will log verbose output");
29
- }
30
- // Note: MCP host configuration is passed from orchestrator
31
- // Runner does not define its own MCP tools
32
- this.setupInternalMcpServer();
33
- }
34
- setupInternalMcpServer() {
35
- // Runner does not define its own MCP tools
36
- // All MCP tool configuration is passed from orchestrator in conversation config
37
- }
38
- async startConversation(conversationObjectType, conversationObjectId, config, initialMessages, conversationData) {
39
- // Returns conversation context
40
- // Greenfield: conversationData.id is required as the authoritative DB conversation ID
41
- if (!conversationData?.id) {
42
- throw new Error("startConversation requires conversationData with a valid conversation.id");
43
- }
44
- // Use sessionId from config if resuming, or agentSessionId from conversationData if available
45
- const agentSessionId = config.sessionId || conversationData.agentSessionId || "";
46
- const conversationId = conversationData.id;
47
- const context = {
48
- conversationId,
49
- agentSessionId, // Will be updated by onSessionId callback for new conversations
50
- conversationObjectType,
51
- conversationObjectId,
52
- taskId: conversationObjectType === "Task" ? conversationObjectId : undefined,
53
- workspaceId: config.workspaceId,
54
- status: "starting",
55
- config,
56
- startedAt: new Date(),
57
- lastActivityAt: new Date(),
58
- // Add conversation details if provided
59
- model: conversationData?.model || "sonnet",
60
- globalInstructions: conversationData?.globalInstructions || "",
61
- workspaceInstructions: conversationData?.workspaceInstructions || "",
62
- permissionsMode: conversationData?.permissionsMode || "all",
63
- };
64
- // Store with conversation.id as the key
65
- this.runner.activeConversations_.set(conversationId, context);
66
- console_1.console.log(`[ClaudeManager] Stored conversation context:`, {
67
- conversationId,
68
- agentSessionId: context.agentSessionId,
69
- conversationObjectType: context.conversationObjectType,
70
- conversationObjectId: context.conversationObjectId,
71
- mapSize: this.runner.activeConversations_.size,
72
- allKeys: Array.from(this.runner.activeConversations_.keys()),
73
- });
74
- const workspaceId = config.workspaceId;
75
- // Checkout repository if specified
76
- let workspacePath;
77
- // Check if this is a local workspace by looking for runnerRepoPath in config
78
- if (config.runnerRepoPath) {
79
- // Local workspace - use the provided path directly
80
- workspacePath = config.runnerRepoPath;
81
- console_1.console.log(`Using local workspace path: ${workspacePath}`);
82
- // For task conversations in local workspaces, create a local task handle
83
- if (conversationObjectType === "Task") {
84
- const taskHandle = await this.repositoryManager.createLocalTaskHandle(conversationObjectId, workspacePath);
85
- // Store task handle information in context for later use
86
- context.taskHandle = taskHandle;
87
- }
88
- }
89
- else if (conversationObjectType === "Task" &&
90
- config.repository &&
91
- workspaceId) {
92
- // Use task-specific worktree for task conversations
93
- console_1.console.log(`[ClaudeManager] Creating task worktree with repository config:`, {
94
- conversationObjectId,
95
- workspaceId,
96
- repository: config.repository,
97
- hasUrl: !!config.repository.url,
98
- url: config.repository.url,
99
- branch: config.repository.branch,
100
- type: config.repository.type,
101
- });
102
- // Check if repository.url is missing
103
- if (!config.repository.url) {
104
- throw new Error(`Repository URL is missing in config for task ${conversationObjectId}. Repository config: ${JSON.stringify(config.repository)}`);
105
- }
106
- const taskHandle = await this.repositoryManager.createTaskWorktree(conversationObjectId, workspaceId, config.repository.url, config.repository.branch, config.githubToken);
107
- workspacePath = taskHandle.worktreePath;
108
- // Store task handle information in context for later use
109
- context.taskHandle = taskHandle;
110
- }
111
- else if (config.repository && workspaceId) {
112
- // Check if it's a local repository
113
- if (config.repository.type === "local" && config.repository.localPath) {
114
- // Use the local path directly
115
- workspacePath = config.repository.localPath;
116
- console_1.console.log(`Using local repository path: ${workspacePath}`);
117
- }
118
- else {
119
- // Fall back to workspace-based checkout for non-task conversations
120
- workspacePath = await this.repositoryManager.checkoutRepository(workspaceId, config.repository.url, config.repository.branch, config.githubToken);
121
- }
122
- }
123
- else if (workspaceId) {
124
- workspacePath = await this.repositoryManager.getWorkspacePath(workspaceId);
125
- }
126
- else {
127
- // Default workspace path when no workspaceId is provided
128
- workspacePath = process.cwd();
129
- }
130
- // Fetch GitHub tokens from orchestrator API if we have a workspaceId
131
- const githubToken = workspaceId
132
- ? await this.fetchGithubTokens(workspaceId)
133
- : undefined;
134
- // Generate TOOL_TOKEN for MCP tools authentication
135
- let toolToken;
136
- if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
137
- const runnerToken = process.env["CLAUDETTE_RUNNER_TOKEN"];
138
- const runnerUid = this.runner.getRunnerUid();
139
- if (runnerToken && runnerUid && context.conversationId) {
140
- // Sign JWT with runner's token
141
- toolToken = jsonwebtoken_1.default.sign({
142
- conversationId: context.conversationId,
143
- runnerUid: runnerUid,
144
- }, runnerToken, {
145
- expiresIn: "60m", // 60 minutes expiry
146
- });
147
- console_1.console.log("[ClaudeManager] Generated TOOL_TOKEN for MCP authentication");
148
- }
149
- else {
150
- console_1.console.warn("[ClaudeManager] Unable to generate TOOL_TOKEN - missing required data");
151
- }
152
- }
153
- // Build claude conversation
154
- const builder = (0, claude_code_sdk_ts_1.claude)()
155
- .withExecutable(require.resolve("@anthropic-ai/claude-code/cli.js"))
156
- .withEnv({
157
- ANTHROPIC_API_KEY: config.anthropicApiKey || "",
158
- ...(githubToken && { GITHUB_TOKEN: githubToken }),
159
- })
160
- .withModel(context.model)
161
- .inDirectory(workspacePath)
162
- .skipPermissions(); // Runner manages permissions
163
- // Enable debug logging when DEBUG is set
164
- if (process.env["DEBUG"] === "true") {
165
- console_1.console.log("[ClaudeManager] Enabling claude-code-sdk debug logging");
166
- console_1.console.log("[ClaudeManager] CLI path:", require.resolve("@anthropic-ai/claude-code/cli.js"));
167
- console_1.console.log("[ClaudeManager] Working directory:", workspacePath);
168
- console_1.console.log("[ClaudeManager] Model:", context.model);
169
- const debugLogger = new claude_code_sdk_ts_1.ConsoleLogger(claude_code_sdk_ts_1.LogLevel.TRACE, "[Claude SDK]");
170
- builder.withLogger(debugLogger);
171
- // Also enable debug mode on the CLI connection to capture stderr/stdout
172
- builder.debug(true);
173
- }
174
- // Add session ID if resuming
175
- if (config.sessionId) {
176
- builder.withSessionId(config.sessionId);
177
- }
178
- // Configure MCP servers from orchestrator
179
- if (config.mcpServers) {
180
- console_1.console.log("[ClaudeManager] MCP servers configuration:", JSON.stringify(config.mcpServers, null, 2));
181
- // Pass MCP configuration directly from orchestrator to Claude SDK
182
- // The runner does not define or modify MCP tools
183
- const expandedMcpServers = (0, expand_env_1.expandEnv)(config.mcpServers, {
184
- TOOL_TOKEN: toolToken,
185
- });
186
- builder.withMCP(expandedMcpServers);
187
- }
188
- // Append system prompts if we have conversation instructions
189
- if (context.globalInstructions || context.workspaceInstructions) {
190
- const combinedInstructions = [
191
- context.globalInstructions,
192
- context.workspaceInstructions,
193
- ]
194
- .filter(Boolean)
195
- .join("\n\n");
196
- if (combinedInstructions) {
197
- builder.appendSystemPrompt(combinedInstructions);
198
- }
199
- }
200
- // Apply read-only restrictions if requested
201
- if (context.permissionsMode === "read") {
202
- builder.denyTools("Write", "Edit", "MultiEdit", "Bash", "KillBash", "NotebookEdit", "ExitPlanMode");
203
- console_1.console.log("[ClaudeManager] Applied read-only mode tool restrictions");
204
- }
205
- // Set up process completion handler
206
- builder.onProcessComplete(async (exitCode, error) => {
207
- // Exit code 143 (SIGTERM) is expected when we stop a conversation
208
- const isExpectedTermination = exitCode === 143 &&
209
- (context.status === "stopped" || context.status === "stopping");
210
- if ((error || exitCode !== 0) && !isExpectedTermination) {
211
- // Log detailed error information
212
- console_1.console.error(`[ClaudeManager] Claude process failed`, {
213
- exitCode,
214
- errorMessage: error?.message,
215
- errorStack: error?.stack,
216
- conversationId: context.conversationId,
217
- agentSessionId: context.agentSessionId,
218
- workspacePath,
219
- model: context.model,
220
- conversationStatus: context.status,
221
- });
222
- // Create a more detailed error message
223
- const errorMessage = error?.message || `Process exited with code ${exitCode}`;
224
- const errorDetails = {
225
- exitCode,
226
- errorStack: error?.stack,
227
- workspacePath,
228
- model: context.model,
229
- sessionId: context.agentSessionId,
230
- conversationId: context.conversationId,
231
- conversationStatus: context.status,
232
- timestamp: new Date().toISOString(),
233
- };
234
- await this.runner.notify("error.report", {
235
- conversationId: context.conversationId,
236
- conversationObjectType: context.conversationObjectType,
237
- conversationObjectId: context.conversationObjectId,
238
- agentSessionId: context.agentSessionId,
239
- errorType: "process_exit",
240
- message: errorMessage,
241
- details: errorDetails,
242
- });
243
- }
244
- else if (isExpectedTermination) {
245
- console_1.console.log(`[ClaudeManager] Claude process terminated as expected (conversation.stop)`, {
246
- exitCode,
247
- conversationId: context.conversationId,
248
- agentSessionId: context.agentSessionId,
249
- conversationStatus: context.status,
250
- });
251
- }
252
- // Notify orchestrator that conversation ended
253
- await this.runner.notify("conversation.end", {
254
- conversationId: context.conversationId,
255
- conversationObjectType: context.conversationObjectType,
256
- conversationObjectId: context.conversationObjectId,
257
- agentSessionId: context.agentSessionId,
258
- isError: exitCode !== 0,
259
- errorMessage: error?.message,
260
- });
261
- // Clean up conversation from active conversations
262
- console_1.console.log(`[ClaudeManager] Removing conversation from active map:`, {
263
- conversationId: context.conversationId,
264
- agentSessionId: context.agentSessionId,
265
- mapSizeBefore: this.runner.activeConversations_.size,
266
- });
267
- this.runner.activeConversations_.delete(context.conversationId);
268
- // Update status line
269
- status_line_1.statusLineManager.updateActiveCount(this.runner.activeConversations_.size);
270
- });
271
- // Create conversation instance
272
- const conversation = builder.asConversation();
273
- // Store conversation instance in context for reuse
274
- context.conversation = conversation;
275
- // Set up session ID callback - this is where we get the real agentSessionId
276
- conversation.onSessionId(async (agentSessionId) => {
277
- if (!agentSessionId)
278
- return;
279
- // Check if this is a new session ID
280
- const oldSessionId = context.agentSessionId;
281
- if (oldSessionId !== agentSessionId) {
282
- // Update context with new session ID
283
- context.agentSessionId = agentSessionId;
284
- context.status = "active";
285
- // Note: We don't need to update the Map key since we're using conversation.id now
286
- // Notify orchestrator of session change
287
- await this.runner.notify("agentSessionId.changed", {
288
- conversationId: context.conversationId,
289
- conversationObjectType,
290
- conversationObjectId,
291
- oldAgentSessionId: oldSessionId,
292
- newAgentSessionId: agentSessionId,
293
- });
294
- }
295
- });
296
- // Set up streaming message handler
297
- const messageHandler = async (message, sessionId) => {
298
- await this.handleStreamedMessage(context, message, sessionId);
299
- };
300
- conversation.stream(messageHandler);
301
- // Note: Error handling is done via process completion handler
302
- // The Claude SDK doesn't have an onError method on conversations
303
- // Send initial messages
304
- try {
305
- for (const message of initialMessages) {
306
- const initialText = this.normalizeToText(message.content);
307
- conversation.send({
308
- type: "text",
309
- text: initialText,
310
- });
311
- }
312
- console_1.console.log(`Started conversation for ${conversationObjectType} ${conversationObjectId} in workspace ${workspacePath}`);
313
- // Return the conversation context directly
314
- return context;
315
- }
316
- catch (error) {
317
- // Handle startup errors
318
- await this._handleConversationError(context, error);
319
- throw error;
320
- }
321
- }
322
- async stopConversation(agentSessionId, context) {
323
- if (context && context.conversation) {
324
- context.status = "stopping";
325
- try {
326
- // Mark conversation as stopped BEFORE ending to prevent race conditions
327
- context.status = "stopped";
328
- // Properly end the conversation using the SDK
329
- await context.conversation.end();
330
- }
331
- catch (error) {
332
- console_1.console.error(`Error ending conversation ${agentSessionId}:`, error);
333
- }
334
- // Clean up conversation reference
335
- delete context.conversation;
336
- }
337
- console_1.console.log(`Stopped conversation ${agentSessionId} for ${context.conversationObjectType} ${context.conversationObjectId}`);
338
- }
339
- async resumeConversation(conversationObjectType, conversationObjectId, agentSessionId, config, conversationData, resumeMessage) {
340
- console_1.console.log(`[ClaudeManager] Resuming conversation ${agentSessionId}`);
341
- // Resume is handled by starting a new conversation with the existing session ID
342
- const context = await this.startConversation(conversationObjectType, conversationObjectId, { ...config, sessionId: agentSessionId }, [], // Don't send initial messages
343
- conversationData);
344
- // After starting the conversation with the sessionId, we need to send a message
345
- // to actually trigger the Claude process to continue
346
- if (context.conversation) {
347
- try {
348
- // Use the provided resume message or default to system instruction
349
- const messageToSend = resumeMessage ||
350
- "<system-instructions>Please continue</system-instructions>";
351
- console_1.console.log(`[ClaudeManager] Sending resume message to conversation ${agentSessionId}`);
352
- context.conversation.send({
353
- type: "text",
354
- text: messageToSend,
355
- });
356
- }
357
- catch (error) {
358
- console_1.console.error(`[ClaudeManager] Error sending resume message:`, error);
359
- }
360
- }
361
- return context.agentSessionId;
362
- }
363
- async sendUserMessage(conversationId, content, config, conversationObjectType, conversationObjectId, conversation) {
364
- console_1.console.log(`[ClaudeManager] sendUserMessage called with:`, {
365
- conversationId,
366
- conversationObjectType,
367
- conversationObjectId,
368
- hasConfig: !!config,
369
- hasConversation: !!conversation,
370
- activeConversations: this.runner.activeConversations_.size,
371
- });
372
- // Find by conversationId only
373
- let context = this.runner.getConversationContext(conversationId);
374
- console_1.console.log(`[ClaudeManager] Lookup by conversationId result:`, {
375
- found: !!context,
376
- conversationId: context?.conversationId,
377
- agentSessionId: context?.agentSessionId,
378
- });
379
- if (!context && conversation) {
380
- // Use provided conversation details
381
- try {
382
- const conversationDetails = conversation;
383
- // For task conversations, fetch the task config to get repository information
384
- let taskConfig = {};
385
- if (conversationDetails.objectType === "Task" ||
386
- conversationDetails.objectType === "task") {
387
- try {
388
- const taskConfigResponse = await this.runner.sendToOrchestrator({
389
- jsonrpc: "2.0",
390
- id: `task-config-${Date.now()}`,
391
- method: "task.getConfig",
392
- params: { taskId: conversationDetails.objectId },
393
- });
394
- if (taskConfigResponse?.result) {
395
- taskConfig = taskConfigResponse.result;
396
- console_1.console.log(`[ClaudeManager] Fetched task config with repository:`, {
397
- hasRepository: !!taskConfigResponse.result.repository,
398
- repositoryType: taskConfigResponse.result.repository?.type,
399
- repositoryPath: taskConfigResponse.result.repository?.localPath,
400
- hasAccessKey: !!taskConfigResponse.result.accessKey,
401
- });
402
- }
403
- }
404
- catch (error) {
405
- console_1.console.error(`[ClaudeManager] Failed to fetch task config:`, error);
406
- }
407
- }
408
- // Bind to existing session if present; do not send a resume message here
409
- const startConfig = {
410
- anthropicApiKey: config?.anthropicApiKey || process.env["ANTHROPIC_API_KEY"] || "",
411
- model: conversationDetails.model,
412
- systemPrompt: config?.systemPrompt,
413
- permissionsMode: conversationDetails.permissionsMode,
414
- accessKey: taskConfig?.accessKey || config?.accessKey || "",
415
- workspaceId: conversationDetails.workspaceId,
416
- ...taskConfig,
417
- ...config,
418
- ...(conversationDetails.agentSessionId
419
- ? { sessionId: conversationDetails.agentSessionId }
420
- : {}),
421
- };
422
- // Start the SDK conversation (no initial messages); this attaches to existing session when provided
423
- await this.startConversation(conversationDetails.objectType, conversationDetails.objectId, startConfig, [], conversationDetails);
424
- // Refresh context after start/resume
425
- context = this.runner.getConversationContext(conversationId);
426
- }
427
- catch (error) {
428
- console_1.console.error(`Failed to fetch conversation ${conversationId}:`, error);
429
- }
430
- }
431
- if (!context) {
432
- throw new Error(`No active or fetchable conversation found for ${conversationId}`);
433
- }
434
- try {
435
- // Send immediately when a conversation instance exists; no need to wait for "active"
436
- if (!context.conversation) {
437
- throw new Error(`No conversation instance found for conversation ${context.conversationId}`);
438
- }
439
- // Guard: Don't send messages if conversation is stopped or stopping
440
- const conversationStatus = context.status;
441
- if (conversationStatus === "stopped" ||
442
- conversationStatus === "stopping") {
443
- console_1.console.warn(`Attempted to send message to stopped/stopping conversation ${context.conversationId}`, {
444
- status: context.status,
445
- conversationObjectType: context.conversationObjectType,
446
- conversationObjectId: context.conversationObjectId,
447
- });
448
- return;
449
- }
450
- // Normalize arbitrary content into a plain string for the CLI
451
- const normalizedText = this.normalizeToText(content);
452
- if (process.env["DEBUG"] === "true") {
453
- console_1.console.log("[ClaudeManager] Normalized follow-up content", {
454
- originalType: typeof content,
455
- isArray: Array.isArray(content) || undefined,
456
- normalizedPreview: typeof normalizedText === "string"
457
- ? normalizedText.slice(0, 160)
458
- : String(normalizedText).slice(0, 160),
459
- });
460
- }
461
- console_1.console.log("[ClaudeManager] Normalized follow-up content", typeof normalizedText, normalizedText);
462
- context.conversation.send({
463
- type: "text",
464
- text: normalizedText,
465
- });
466
- // Update last activity timestamp
467
- context.lastActivityAt = new Date();
468
- console_1.console.log(`Sent user message to conversation ${context.conversationId} (agentSessionId: ${context.agentSessionId}, status: ${context.status})`);
469
- }
470
- catch (error) {
471
- // Handle errors properly
472
- await this._handleConversationError(context, error);
473
- throw error; // Re-throw to maintain the same behavior
474
- }
475
- }
476
- async fetchGithubTokens(workspaceId) {
477
- try {
478
- const response = await fetch(`${this.runner.config_.orchestratorUrl}/api/runner/tokens?workspaceId=${workspaceId}`, {
479
- method: "GET",
480
- headers: {
481
- Authorization: `Bearer ${process.env["CLAUDETTE_RUNNER_TOKEN"]}`,
482
- },
483
- });
484
- if (!response.ok) {
485
- console_1.console.error(`Failed to fetch GitHub tokens: ${response.status}`);
486
- return undefined;
487
- }
488
- const data = (await response.json());
489
- return data.githubToken;
490
- }
491
- catch (error) {
492
- console_1.console.error("Error fetching GitHub tokens:", error);
493
- return undefined;
494
- }
495
- }
496
- async _handleConversationError(context, error) {
497
- const errorType = this.classifyError(error);
498
- // Notify orchestrator
499
- await this.runner.notify("error.report", {
500
- conversationId: context.conversationId,
501
- conversationObjectType: context.conversationObjectType,
502
- conversationObjectId: context.conversationObjectId,
503
- agentSessionId: context.agentSessionId,
504
- errorType,
505
- message: error.message,
506
- details: {
507
- stack: error.stack,
508
- timestamp: new Date(),
509
- },
510
- });
511
- // Conversation continues on error - no automatic cleanup
512
- console_1.console.error(`Conversation error for ${context.conversationObjectType} ${context.conversationObjectId}:`, error);
513
- }
514
- classifyError(error) {
515
- if (error.message.includes("process exited")) {
516
- return "process_exit";
517
- }
518
- else if (error.message.includes("model")) {
519
- return "model_error";
520
- }
521
- else if (error.message.includes("tool")) {
522
- return "tool_error";
523
- }
524
- else if (error.message.includes("permission")) {
525
- return "permission_error";
526
- }
527
- else if (error.message.includes("timeout")) {
528
- return "timeout_error";
529
- }
530
- return "unknown_error";
531
- }
532
- /**
533
- * Normalize arbitrary content shapes into a plain string for the CLI
534
- */
535
- normalizeToText(value) {
536
- if (typeof value === "string")
537
- return value;
538
- if (value == null)
539
- return "";
540
- if (typeof value === "object") {
541
- // Common simple shapes
542
- if (typeof value.text === "string")
543
- return value.text;
544
- if (typeof value.text === "object" &&
545
- value.text &&
546
- typeof value.text.text === "string")
547
- return value.text.text;
548
- if (typeof value.content === "string")
549
- return value.content;
550
- // Array of blocks: [{ type: 'text', text: '...' }, ...]
551
- if (Array.isArray(value)) {
552
- const texts = value
553
- .map((b) => b && b.type === "text" && typeof b.text === "string" ? b.text : null)
554
- .filter((t) => !!t);
555
- if (texts.length)
556
- return texts.join(" ");
557
- }
558
- // Nested message shapes
559
- if (typeof value.message === "object" &&
560
- value.message &&
561
- typeof value.message.text === "string") {
562
- return value.message.text;
563
- }
564
- }
565
- try {
566
- return JSON.stringify(value);
567
- }
568
- catch {
569
- return String(value);
570
- }
571
- }
572
- async handleStreamedMessage(context, message, sessionId) {
573
- // Guard: Don't process messages if conversation is stopped or stopping
574
- const status = context.status;
575
- if (status === "stopped" || status === "stopping") {
576
- console_1.console.log(`Ignoring message for stopped/stopping conversation ${context.conversationId}`, {
577
- status: context.status,
578
- messageType: message.type,
579
- });
580
- return;
581
- }
582
- try {
583
- console_1.console.log(`Received streamed message for ${context.conversationObjectType} ${context.conversationObjectId}`, {
584
- type: message.type,
585
- });
586
- // Build structured content based on message type
587
- let messageType = message.type;
588
- let subtype;
589
- let structuredContent = {};
590
- let isError = false;
591
- // Extract content based on message type
592
- switch (message.type) {
593
- case "assistant": {
594
- const assistantMsg = message;
595
- if (assistantMsg.content) {
596
- // Extract text content from content blocks
597
- const textBlocks = assistantMsg.content.filter((b) => b.type === "text");
598
- const textContent = textBlocks.map((b) => b.text).join("");
599
- // Extract tool calls
600
- const toolUseBlocks = assistantMsg.content.filter((b) => b.type === "tool_use");
601
- const toolCalls = toolUseBlocks.length > 0
602
- ? toolUseBlocks.map((b) => ({
603
- id: b.id,
604
- name: b.name,
605
- arguments: b.input,
606
- }))
607
- : undefined;
608
- structuredContent = {
609
- text: textContent,
610
- toolCalls,
611
- timestamp: new Date().toISOString(),
612
- };
613
- }
614
- break;
615
- }
616
- case "thinking": {
617
- // Map thinking to assistant with subtype
618
- messageType = "assistant";
619
- subtype = "thinking";
620
- const thinkingMsg = message;
621
- structuredContent = {
622
- text: thinkingMsg.content || "",
623
- timestamp: new Date().toISOString(),
624
- };
625
- break;
626
- }
627
- case "tool_use": {
628
- // Tool call request - map to assistant
629
- messageType = "assistant";
630
- subtype = "tool_use";
631
- const toolUseMsg = message;
632
- structuredContent = {
633
- toolCalls: [
634
- {
635
- id: toolUseMsg.id,
636
- name: toolUseMsg.name,
637
- arguments: toolUseMsg.input,
638
- },
639
- ],
640
- timestamp: new Date().toISOString(),
641
- };
642
- break;
643
- }
644
- case "tool_result": {
645
- // Tool execution result - map to tool_result
646
- messageType = "tool_result";
647
- subtype = "tool_result";
648
- const toolResultMsg = message;
649
- structuredContent = {
650
- toolResults: [
651
- {
652
- toolCallId: toolResultMsg.tool_use_id,
653
- result: toolResultMsg.content,
654
- isError: toolResultMsg.is_error || false,
655
- },
656
- ],
657
- timestamp: new Date().toISOString(),
658
- };
659
- break;
660
- }
661
- case "result": {
662
- const resultMsg = message;
663
- structuredContent = {
664
- text: resultMsg.content || resultMsg.result || "",
665
- timestamp: new Date().toISOString(),
666
- };
667
- break;
668
- }
669
- case "user": {
670
- const userMsg = message;
671
- // Check if content is already an array (e.g., tool results)
672
- if (Array.isArray(userMsg.content)) {
673
- // Check if this is a tool result by examining content
674
- const hasToolResult = userMsg.content.some((item) => item && typeof item === "object" && item.type === "tool_result");
675
- if (hasToolResult) {
676
- // This is a tool result - change message type
677
- messageType = "tool_result";
678
- subtype = "tool_result";
679
- }
680
- // Pass the array directly as structured content
681
- structuredContent = userMsg.content;
682
- }
683
- else if (typeof userMsg.content === "string") {
684
- // String content - wrap in text object
685
- structuredContent = {
686
- text: userMsg.content,
687
- timestamp: new Date().toISOString(),
688
- };
689
- }
690
- else {
691
- // Other object content - preserve as is
692
- structuredContent = userMsg.content || {};
693
- }
694
- break;
695
- }
696
- case "system": {
697
- const systemMsg = message;
698
- structuredContent = {
699
- text: systemMsg.content || "",
700
- timestamp: new Date().toISOString(),
701
- };
702
- break;
703
- }
704
- case "error": {
705
- const errorMsg = message;
706
- messageType = "system";
707
- subtype = "error";
708
- isError = true;
709
- structuredContent = {
710
- text: errorMsg.message || errorMsg.error || "Unknown error",
711
- errorType: errorMsg.error_type || "unknown",
712
- errorDetails: {
713
- stack: errorMsg.stack,
714
- code: errorMsg.code,
715
- context: errorMsg,
716
- },
717
- timestamp: new Date().toISOString(),
718
- };
719
- break;
720
- }
721
- default: {
722
- // Unknown message type - log and send as assistant
723
- const unknownMsg = message;
724
- console_1.console.warn(`Unknown message type: ${unknownMsg.type}`, message);
725
- messageType = "assistant";
726
- structuredContent = {
727
- text: JSON.stringify(message),
728
- timestamp: new Date().toISOString(),
729
- };
730
- }
731
- }
732
- // Generate a unique message ID
733
- const messageId = `${context.agentSessionId}-${Date.now()}-${Math.random()
734
- .toString(36)
735
- .substr(2, 9)}`;
736
- // Send agent message to orchestrator with structured content
737
- // Skip if conversation is stopping/stopped to avoid race conditions
738
- const currentStatus = context.status;
739
- if (currentStatus !== "stopped" && currentStatus !== "stopping") {
740
- await this.runner.notify("message.agent", {
741
- conversationId: context.conversationId,
742
- conversationObjectType: context.conversationObjectType,
743
- conversationObjectId: context.conversationObjectId,
744
- agentSessionId: context.agentSessionId,
745
- type: messageType,
746
- subtype,
747
- content: Array.isArray(structuredContent)
748
- ? structuredContent
749
- : [structuredContent],
750
- messageId,
751
- isError,
752
- });
753
- }
754
- // Tool calls are now handled directly by Claude through the MCP server
755
- // We just log that we saw them but don't intercept or process them
756
- if (structuredContent.toolCalls &&
757
- structuredContent.toolCalls.length > 0) {
758
- console_1.console.log(`Claude is making ${structuredContent.toolCalls.length} tool call(s) via MCP`, {
759
- conversationObjectId: context.conversationObjectId,
760
- toolNames: structuredContent.toolCalls.map((tc) => tc.name),
761
- });
762
- }
763
- }
764
- catch (error) {
765
- // Check if this is a transport error due to stopped conversation
766
- const errorMessage = error instanceof Error ? error.message : String(error);
767
- const isTransportError = errorMessage.includes("Cannot read properties of undefined") ||
768
- errorMessage.includes("stdout") ||
769
- errorMessage.includes("transport");
770
- const statusCheck = context.status;
771
- if (isTransportError &&
772
- (statusCheck === "stopped" || statusCheck === "stopping")) {
773
- // This is expected when conversation is stopped - just log it
774
- console_1.console.log(`Transport error for stopped/stopping conversation ${context.conversationId} (expected):`, errorMessage);
775
- return;
776
- }
777
- console_1.console.error(`Error handling streamed message for ${context.conversationObjectType} ${context.conversationObjectId}:`, error);
778
- await this._handleConversationError(context, error);
779
- }
780
- }
781
- }
782
- exports.ClaudeManager = ClaudeManager;
783
- //# sourceMappingURL=claude-manager.js.map