@northflare/runner 0.0.1

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 (154) hide show
  1. package/DEBUG_LOGGING.md +60 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION_PLAN.md +52 -0
  4. package/README.md +220 -0
  5. package/SDK_IMPLEMENTATION_GUIDE.md +1036 -0
  6. package/bin/northflare-runner +367 -0
  7. package/coverage/base.css +224 -0
  8. package/coverage/block-navigation.js +87 -0
  9. package/coverage/coverage-final.json +12 -0
  10. package/coverage/favicon.png +0 -0
  11. package/coverage/index.html +176 -0
  12. package/coverage/lib/index.html +116 -0
  13. package/coverage/lib/preload-script.js.html +964 -0
  14. package/coverage/prettify.css +1 -0
  15. package/coverage/prettify.js +2 -0
  16. package/coverage/sort-arrow-sprite.png +0 -0
  17. package/coverage/sorter.js +196 -0
  18. package/coverage/src/collections/index.html +116 -0
  19. package/coverage/src/collections/runner-messages.ts.html +312 -0
  20. package/coverage/src/components/claude-manager.ts.html +1290 -0
  21. package/coverage/src/components/index.html +146 -0
  22. package/coverage/src/components/message-handler.ts.html +730 -0
  23. package/coverage/src/components/repository-manager.ts.html +841 -0
  24. package/coverage/src/index.html +131 -0
  25. package/coverage/src/index.ts.html +448 -0
  26. package/coverage/src/runner.ts.html +1239 -0
  27. package/coverage/src/utils/config.ts.html +780 -0
  28. package/coverage/src/utils/console.ts.html +121 -0
  29. package/coverage/src/utils/index.html +161 -0
  30. package/coverage/src/utils/logger.ts.html +475 -0
  31. package/coverage/src/utils/status-line.ts.html +445 -0
  32. package/dist/collections/runner-messages.d.ts +52 -0
  33. package/dist/collections/runner-messages.d.ts.map +1 -0
  34. package/dist/collections/runner-messages.js +161 -0
  35. package/dist/collections/runner-messages.js.map +1 -0
  36. package/dist/components/claude-manager.d.ts +39 -0
  37. package/dist/components/claude-manager.d.ts.map +1 -0
  38. package/dist/components/claude-manager.js +783 -0
  39. package/dist/components/claude-manager.js.map +1 -0
  40. package/dist/components/claude-sdk-manager.d.ts +47 -0
  41. package/dist/components/claude-sdk-manager.d.ts.map +1 -0
  42. package/dist/components/claude-sdk-manager.js +1088 -0
  43. package/dist/components/claude-sdk-manager.js.map +1 -0
  44. package/dist/components/enhanced-repository-manager.d.ts +134 -0
  45. package/dist/components/enhanced-repository-manager.d.ts.map +1 -0
  46. package/dist/components/enhanced-repository-manager.js +602 -0
  47. package/dist/components/enhanced-repository-manager.js.map +1 -0
  48. package/dist/components/message-handler-sse.d.ts +46 -0
  49. package/dist/components/message-handler-sse.d.ts.map +1 -0
  50. package/dist/components/message-handler-sse.js +734 -0
  51. package/dist/components/message-handler-sse.js.map +1 -0
  52. package/dist/components/message-handler.d.ts +35 -0
  53. package/dist/components/message-handler.d.ts.map +1 -0
  54. package/dist/components/message-handler.js +689 -0
  55. package/dist/components/message-handler.js.map +1 -0
  56. package/dist/components/repository-manager.d.ts +51 -0
  57. package/dist/components/repository-manager.d.ts.map +1 -0
  58. package/dist/components/repository-manager.js +295 -0
  59. package/dist/components/repository-manager.js.map +1 -0
  60. package/dist/index.d.ts +9 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +166 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/runner-sse.d.ts +57 -0
  65. package/dist/runner-sse.d.ts.map +1 -0
  66. package/dist/runner-sse.js +698 -0
  67. package/dist/runner-sse.js.map +1 -0
  68. package/dist/runner.d.ts +51 -0
  69. package/dist/runner.d.ts.map +1 -0
  70. package/dist/runner.js +530 -0
  71. package/dist/runner.js.map +1 -0
  72. package/dist/services/RunnerAPIClient.d.ts +30 -0
  73. package/dist/services/RunnerAPIClient.d.ts.map +1 -0
  74. package/dist/services/RunnerAPIClient.js +112 -0
  75. package/dist/services/RunnerAPIClient.js.map +1 -0
  76. package/dist/services/SSEClient.d.ts +60 -0
  77. package/dist/services/SSEClient.d.ts.map +1 -0
  78. package/dist/services/SSEClient.js +204 -0
  79. package/dist/services/SSEClient.js.map +1 -0
  80. package/dist/types/claude.d.ts +45 -0
  81. package/dist/types/claude.d.ts.map +1 -0
  82. package/dist/types/claude.js +6 -0
  83. package/dist/types/claude.js.map +1 -0
  84. package/dist/types/index.d.ts +47 -0
  85. package/dist/types/index.d.ts.map +1 -0
  86. package/dist/types/index.js +23 -0
  87. package/dist/types/index.js.map +1 -0
  88. package/dist/types/messages.d.ts +31 -0
  89. package/dist/types/messages.d.ts.map +1 -0
  90. package/dist/types/messages.js +6 -0
  91. package/dist/types/messages.js.map +1 -0
  92. package/dist/types/runner-interface.d.ts +24 -0
  93. package/dist/types/runner-interface.d.ts.map +1 -0
  94. package/dist/types/runner-interface.js +6 -0
  95. package/dist/types/runner-interface.js.map +1 -0
  96. package/dist/utils/StateManager.d.ts +52 -0
  97. package/dist/utils/StateManager.d.ts.map +1 -0
  98. package/dist/utils/StateManager.js +162 -0
  99. package/dist/utils/StateManager.js.map +1 -0
  100. package/dist/utils/config.d.ts +41 -0
  101. package/dist/utils/config.d.ts.map +1 -0
  102. package/dist/utils/config.js +250 -0
  103. package/dist/utils/config.js.map +1 -0
  104. package/dist/utils/console.d.ts +11 -0
  105. package/dist/utils/console.d.ts.map +1 -0
  106. package/dist/utils/console.js +15 -0
  107. package/dist/utils/console.js.map +1 -0
  108. package/dist/utils/expand-env.d.ts +2 -0
  109. package/dist/utils/expand-env.d.ts.map +1 -0
  110. package/dist/utils/expand-env.js +20 -0
  111. package/dist/utils/expand-env.js.map +1 -0
  112. package/dist/utils/logger.d.ts +9 -0
  113. package/dist/utils/logger.d.ts.map +1 -0
  114. package/dist/utils/logger.js +108 -0
  115. package/dist/utils/logger.js.map +1 -0
  116. package/dist/utils/status-line.d.ts +37 -0
  117. package/dist/utils/status-line.d.ts.map +1 -0
  118. package/dist/utils/status-line.js +113 -0
  119. package/dist/utils/status-line.js.map +1 -0
  120. package/docs/claude-manager.md +91 -0
  121. package/exceptions.log +22 -0
  122. package/lib/preload-script.js +293 -0
  123. package/package.json +55 -0
  124. package/rejections.log +63 -0
  125. package/runner.log +488 -0
  126. package/src/components/claude-sdk-manager.ts +1354 -0
  127. package/src/components/enhanced-repository-manager.ts +823 -0
  128. package/src/components/message-handler-sse.ts +1011 -0
  129. package/src/components/repository-manager.ts +337 -0
  130. package/src/index.ts +166 -0
  131. package/src/runner-sse.ts +847 -0
  132. package/src/services/RunnerAPIClient.ts +135 -0
  133. package/src/services/SSEClient.ts +258 -0
  134. package/src/types/claude.ts +55 -0
  135. package/src/types/computer-name.d.ts +4 -0
  136. package/src/types/index.ts +63 -0
  137. package/src/types/messages.ts +39 -0
  138. package/src/types/runner-interface.ts +34 -0
  139. package/src/utils/StateManager.ts +187 -0
  140. package/src/utils/codex-sdk.js +448 -0
  141. package/src/utils/config.ts +315 -0
  142. package/src/utils/console.ts +13 -0
  143. package/src/utils/expand-env.ts +22 -0
  144. package/src/utils/logger.ts +131 -0
  145. package/src/utils/sdk-demo.js +34 -0
  146. package/src/utils/status-line.ts +121 -0
  147. package/test-debug.sh +26 -0
  148. package/tests/retry-strategies.test.ts +410 -0
  149. package/tests/sdk-integration.test.ts +329 -0
  150. package/tests/sdk-streaming.test.ts +1180 -0
  151. package/tests/setup.ts +5 -0
  152. package/tests/test-claude-manager.ts +120 -0
  153. package/tsconfig.json +36 -0
  154. package/vitest.config.ts +27 -0
@@ -0,0 +1,1088 @@
1
+ "use strict";
2
+ /**
3
+ * ClaudeManager - Manages Claude conversations using SDK-native patterns
4
+ *
5
+ * This component handles stateful conversation lifecycle management, maintaining
6
+ * persistent conversation instances indexed by taskId. Uses the SDK's native
7
+ * capabilities including the builder pattern, native session management, and
8
+ * built-in streaming instead of custom wrappers.
9
+ *
10
+ * Key improvements:
11
+ * - Uses claude() builder pattern for simplified configuration
12
+ * - Native session management with withSessionId()
13
+ * - Direct AsyncGenerator streaming without custom wrappers
14
+ * - SDK's onProcessComplete() for proper cleanup
15
+ * - Simplified error handling while maintaining compatibility
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.ClaudeManager = void 0;
52
+ const claude_agent_sdk_1 = require("@anthropic-ai/claude-agent-sdk");
53
+ const status_line_1 = require("../utils/status-line");
54
+ const console_1 = require("../utils/console");
55
+ const expand_env_1 = require("../utils/expand-env");
56
+ const jwt = __importStar(require("jsonwebtoken"));
57
+ class ClaudeManager {
58
+ runner;
59
+ repositoryManager;
60
+ constructor(runner, repositoryManager) {
61
+ this.runner = runner;
62
+ this.repositoryManager = repositoryManager;
63
+ // Log debug mode status
64
+ if (process.env["DEBUG"] === "true") {
65
+ console_1.console.log("[ClaudeManager] DEBUG MODE ENABLED - Claude SDK will log verbose output");
66
+ }
67
+ // Note: MCP host configuration is passed from orchestrator
68
+ // Runner does not define its own MCP tools
69
+ this.setupInternalMcpServer();
70
+ }
71
+ setupInternalMcpServer() {
72
+ // Runner does not define its own MCP tools
73
+ // All MCP tool configuration is passed from orchestrator in conversation config
74
+ }
75
+ async startConversation(conversationObjectType, conversationObjectId, config, initialMessages, conversationData) {
76
+ // Returns conversation context
77
+ // Greenfield: conversationData.id is required as the authoritative DB conversation ID
78
+ if (!conversationData?.id) {
79
+ throw new Error("startConversation requires conversationData with a valid conversation.id");
80
+ }
81
+ // Use sessionId from config if resuming, or agentSessionId from conversationData if available
82
+ const agentSessionId = config.sessionId || conversationData.agentSessionId || "";
83
+ const conversationId = conversationData.id;
84
+ const context = {
85
+ conversationId,
86
+ agentSessionId, // Will be updated by onSessionId callback for new conversations
87
+ conversationObjectType,
88
+ conversationObjectId,
89
+ taskId: conversationObjectType === "Task" ? conversationObjectId : undefined,
90
+ workspaceId: config.workspaceId,
91
+ status: "starting",
92
+ config,
93
+ startedAt: new Date(),
94
+ lastActivityAt: new Date(),
95
+ // Add conversation details if provided
96
+ model: conversationData?.model || "sonnet",
97
+ globalInstructions: conversationData?.globalInstructions || "",
98
+ workspaceInstructions: conversationData?.workspaceInstructions || "",
99
+ permissionsMode: conversationData?.permissionsMode || "all",
100
+ };
101
+ // Store with conversation.id as the key
102
+ this.runner.activeConversations_.set(conversationId, context);
103
+ console_1.console.log(`[ClaudeManager] Stored conversation context:`, {
104
+ conversationId,
105
+ agentSessionId: context.agentSessionId,
106
+ conversationObjectType: context.conversationObjectType,
107
+ conversationObjectId: context.conversationObjectId,
108
+ mapSize: this.runner.activeConversations_.size,
109
+ allKeys: Array.from(this.runner.activeConversations_.keys()),
110
+ });
111
+ const workspaceId = config.workspaceId;
112
+ // Checkout repository if specified
113
+ let workspacePath;
114
+ // Check if this is a local workspace by looking for runnerRepoPath in config
115
+ if (config.runnerRepoPath) {
116
+ // Local workspace - use the provided path directly
117
+ workspacePath = config.runnerRepoPath;
118
+ console_1.console.log(`Using local workspace path: ${workspacePath}`);
119
+ // For task conversations in local workspaces, create a local task handle
120
+ if (conversationObjectType === "Task") {
121
+ const taskHandle = await this.repositoryManager.createLocalTaskHandle(conversationObjectId, workspacePath);
122
+ // Store task handle information in context for later use
123
+ context.taskHandle = taskHandle;
124
+ }
125
+ }
126
+ else if (conversationObjectType === "Task" &&
127
+ config.repository &&
128
+ workspaceId) {
129
+ // Use task-specific worktree for task conversations
130
+ console_1.console.log(`[ClaudeManager] Creating task worktree with repository config:`, {
131
+ conversationObjectId,
132
+ workspaceId,
133
+ repository: config.repository,
134
+ hasUrl: !!config.repository.url,
135
+ url: config.repository.url,
136
+ branch: config.repository.branch,
137
+ type: config.repository.type,
138
+ });
139
+ // Check if repository.url is missing
140
+ if (!config.repository.url) {
141
+ throw new Error(`Repository URL is missing in config for task ${conversationObjectId}. Repository config: ${JSON.stringify(config.repository)}`);
142
+ }
143
+ const taskHandle = await this.repositoryManager.createTaskWorktree(conversationObjectId, workspaceId, config.repository.url, config.repository.branch, config.githubToken);
144
+ workspacePath = taskHandle.worktreePath;
145
+ // Store task handle information in context for later use
146
+ context.taskHandle = taskHandle;
147
+ }
148
+ else if (config.repository && workspaceId) {
149
+ // Check if it's a local repository
150
+ if (config.repository.type === "local" && config.repository.localPath) {
151
+ // Use the local path directly
152
+ workspacePath = config.repository.localPath;
153
+ console_1.console.log(`Using local repository path: ${workspacePath}`);
154
+ }
155
+ else {
156
+ // Fall back to workspace-based checkout for non-task conversations
157
+ workspacePath = await this.repositoryManager.checkoutRepository(workspaceId, config.repository.url, config.repository.branch, config.githubToken);
158
+ }
159
+ }
160
+ else if (workspaceId) {
161
+ workspacePath = await this.repositoryManager.getWorkspacePath(workspaceId);
162
+ }
163
+ else {
164
+ // Default workspace path when no workspaceId is provided
165
+ workspacePath = process.cwd();
166
+ }
167
+ // Fetch GitHub tokens from orchestrator API if we have a workspaceId
168
+ const githubToken = workspaceId
169
+ ? await this.fetchGithubTokens(workspaceId)
170
+ : undefined;
171
+ // Generate TOOL_TOKEN for MCP tools authentication
172
+ let toolToken;
173
+ if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
174
+ const runnerToken = process.env["NORTHFLARE_RUNNER_TOKEN"];
175
+ const runnerUid = this.runner.getRunnerUid();
176
+ if (runnerToken && runnerUid && context.conversationId) {
177
+ // Sign JWT with runner's token
178
+ toolToken = jwt.sign({
179
+ conversationId: context.conversationId,
180
+ runnerUid: runnerUid,
181
+ }, runnerToken, {
182
+ expiresIn: "60m", // 60 minutes expiry
183
+ });
184
+ console_1.console.log("[ClaudeManager] Generated TOOL_TOKEN for MCP authentication");
185
+ }
186
+ else {
187
+ console_1.console.warn("[ClaudeManager] Unable to generate TOOL_TOKEN - missing required data");
188
+ }
189
+ }
190
+ // Simplified SDK configuration - using native query API with streamlined options
191
+ // const cliPath = require.resolve("@anthropic-ai/claude-code/cli.js");
192
+ // Debug logging for executable paths
193
+ //console.log("[ClaudeManager] SDK executable paths:", {
194
+ // cliPath,
195
+ // cwd: workspacePath,
196
+ //});
197
+ // Simplified environment configuration
198
+ const envVars = {
199
+ ...Object.fromEntries(Object.entries(process.env).filter(([_, value]) => value !== undefined)), // Preserve parent environment, filter out undefined values
200
+ };
201
+ if (config.anthropicApiKey) {
202
+ envVars["ANTHROPIC_API_KEY"] = config.anthropicApiKey;
203
+ }
204
+ if (config.accessToken) {
205
+ envVars["CLAUDE_CODE_OAUTH_TOKEN"] = config.accessToken;
206
+ }
207
+ if (githubToken) {
208
+ envVars["GITHUB_TOKEN"] = githubToken;
209
+ }
210
+ if (process.env["DEBUG"] === "true") {
211
+ envVars["DEBUG"] = "1";
212
+ }
213
+ // Simplified system prompt handling
214
+ const appendSystemPrompt = [
215
+ context.globalInstructions,
216
+ context.workspaceInstructions,
217
+ ]
218
+ .filter(Boolean)
219
+ .join("\n\n");
220
+ // Simplified tool restrictions for read-only mode
221
+ const disallowedTools = context.permissionsMode === "read"
222
+ ? [
223
+ "Write",
224
+ "Edit",
225
+ "MultiEdit",
226
+ "Bash",
227
+ "KillBash",
228
+ "NotebookEdit",
229
+ "ExitPlanMode",
230
+ ]
231
+ : [];
232
+ if (disallowedTools.length) {
233
+ console_1.console.log("[ClaudeManager] Applied read-only mode tool restrictions");
234
+ }
235
+ // Simplified MCP server configuration
236
+ let mcpServers;
237
+ if (config.mcpServers) {
238
+ console_1.console.log("[ClaudeManager] MCP servers configuration:", JSON.stringify(config.mcpServers, null, 2));
239
+ mcpServers = (0, expand_env_1.expandEnv)(config.mcpServers, { TOOL_TOKEN: toolToken });
240
+ }
241
+ // Create input stream for user messages (simplified message queueing)
242
+ const input = createUserMessageStream();
243
+ // Launch SDK with simplified configuration
244
+ const sdk = (0, claude_agent_sdk_1.query)({
245
+ prompt: input.iterable,
246
+ options: {
247
+ //pathToClaudeCodeExecutable: cliPath,
248
+ cwd: workspacePath,
249
+ env: envVars,
250
+ model: context.model,
251
+ permissionMode: "bypassPermissions", // Runner handles permissions
252
+ resume: config.sessionId || conversationData?.agentSessionId || undefined,
253
+ ...(appendSystemPrompt ? { appendSystemPrompt } : {}),
254
+ ...(disallowedTools.length ? { disallowedTools } : {}),
255
+ ...(mcpServers ? { mcpServers } : {}),
256
+ ...(process.env["DEBUG"] === "true"
257
+ ? {
258
+ stderr: (data) => {
259
+ try {
260
+ console_1.console.log(`[Claude SDK] ${data}`);
261
+ }
262
+ catch { }
263
+ },
264
+ }
265
+ : {}),
266
+ },
267
+ });
268
+ // Simplified conversation wrapper - reduced complexity while maintaining interface
269
+ const conversation = createConversationWrapper(sdk, input, async (hadError, error) => {
270
+ await this._finalizeConversation(context, hadError, error);
271
+ });
272
+ // Store conversation instance in context for reuse
273
+ context.conversation = conversation;
274
+ // Observe session id from first messages that include it
275
+ conversation.onSessionId(async (agentSessionId) => {
276
+ if (!agentSessionId)
277
+ return;
278
+ const oldSessionId = context.agentSessionId;
279
+ if (oldSessionId !== agentSessionId) {
280
+ context.agentSessionId = agentSessionId;
281
+ context.status = "active";
282
+ await this.runner.notify("agentSessionId.changed", {
283
+ conversationId: context.conversationId,
284
+ conversationObjectType,
285
+ conversationObjectId,
286
+ oldAgentSessionId: oldSessionId,
287
+ newAgentSessionId: agentSessionId,
288
+ });
289
+ }
290
+ });
291
+ // Set up streaming message handler
292
+ const messageHandler = async (message, sessionId) => {
293
+ await this.handleStreamedMessage(context, message, sessionId);
294
+ // Heuristic: detect terminal system events and finalize
295
+ try {
296
+ if (message?.type === "system") {
297
+ const m = message;
298
+ const subtype = m.subtype || m?.message?.subtype || m?.event;
299
+ const exitSignals = [
300
+ "exit",
301
+ "exiting",
302
+ "session_end",
303
+ "conversation_end",
304
+ "process_exit",
305
+ "done",
306
+ "completed",
307
+ ];
308
+ if (subtype && exitSignals.includes(String(subtype))) {
309
+ await this._finalizeConversation(context, false);
310
+ }
311
+ }
312
+ else if (message?.type === "result") {
313
+ // Treat 'result' as an end-of-conversation signal for the SDK
314
+ try {
315
+ await this._finalizeConversation(context, false);
316
+ console_1.console.log("[ClaudeManager] Finalized conversation due to SDK 'result' message", {
317
+ conversationId: context.conversationId,
318
+ agentSessionId: context.agentSessionId,
319
+ });
320
+ }
321
+ catch (e) {
322
+ console_1.console.warn("[ClaudeManager] Error finalizing on 'result' message:", e);
323
+ }
324
+ }
325
+ }
326
+ catch (e) {
327
+ console_1.console.warn("[ClaudeManager] finalize-on-system heuristic error:", e);
328
+ }
329
+ };
330
+ conversation.stream(messageHandler);
331
+ // Note: Error handling is done via process completion handler
332
+ // The Claude SDK doesn't have an onError method on conversations
333
+ // Send initial messages
334
+ try {
335
+ for (const message of initialMessages) {
336
+ const initialText = this.normalizeToText(message.content);
337
+ conversation.send({
338
+ type: "text",
339
+ text: initialText,
340
+ });
341
+ }
342
+ console_1.console.log(`Started conversation for ${conversationObjectType} ${conversationObjectId} in workspace ${workspacePath}`);
343
+ // Return the conversation context directly
344
+ return context;
345
+ }
346
+ catch (error) {
347
+ // Handle startup errors
348
+ await this._handleConversationError(context, error);
349
+ throw error;
350
+ }
351
+ }
352
+ async stopConversation(agentSessionId, context, isRunnerShutdown = false, reason) {
353
+ if (context && context.conversation) {
354
+ context.status = "stopping";
355
+ try {
356
+ // Proactively send end notification to avoid missing it on abnormal exits
357
+ // Use provided reason, or fall back to 'runner_shutdown' if isRunnerShutdown is true
358
+ const finalReason = reason || (isRunnerShutdown ? "runner_shutdown" : undefined);
359
+ await this._finalizeConversation(context, false, undefined, finalReason).catch(() => { });
360
+ // Mark conversation as stopped BEFORE ending to prevent race conditions
361
+ context.status = "stopped";
362
+ // Properly end the conversation using the SDK
363
+ await context.conversation.end();
364
+ }
365
+ catch (error) {
366
+ console_1.console.error(`Error ending conversation ${agentSessionId}:`, error);
367
+ }
368
+ // Clean up conversation reference
369
+ delete context.conversation;
370
+ }
371
+ console_1.console.log(`Stopped conversation ${agentSessionId} for ${context.conversationObjectType} ${context.conversationObjectId}`);
372
+ }
373
+ async resumeConversation(conversationObjectType, conversationObjectId, agentSessionId, config, conversationData, resumeMessage) {
374
+ console_1.console.log(`[ClaudeManager] Resuming conversation ${agentSessionId}`);
375
+ // Resume is handled by starting a new conversation with the existing session ID
376
+ const context = await this.startConversation(conversationObjectType, conversationObjectId, { ...config, sessionId: agentSessionId }, [], // Don't send initial messages
377
+ conversationData);
378
+ // After starting the conversation with the sessionId, we need to send a message
379
+ // to actually trigger the Claude process to continue
380
+ if (context.conversation) {
381
+ try {
382
+ // Use the provided resume message or default to system instruction
383
+ const messageToSend = resumeMessage ||
384
+ "<system-instructions>Please continue</system-instructions>";
385
+ console_1.console.log(`[ClaudeManager] Sending resume message to conversation ${agentSessionId}`);
386
+ context.conversation.send({
387
+ type: "text",
388
+ text: messageToSend,
389
+ });
390
+ }
391
+ catch (error) {
392
+ console_1.console.error(`[ClaudeManager] Error sending resume message:`, error);
393
+ }
394
+ }
395
+ return context.agentSessionId;
396
+ }
397
+ async _finalizeConversation(context, hadError, error, reason) {
398
+ // Ensure idempotency
399
+ if (context._finalized)
400
+ return;
401
+ context._finalized = true;
402
+ try {
403
+ await this.runner.notify("conversation.end", {
404
+ conversationId: context.conversationId,
405
+ conversationObjectType: context.conversationObjectType,
406
+ conversationObjectId: context.conversationObjectId,
407
+ agentSessionId: context.agentSessionId,
408
+ isError: hadError,
409
+ errorMessage: error?.message,
410
+ reason: reason,
411
+ });
412
+ }
413
+ catch (e) {
414
+ console_1.console.error("[ClaudeManager] Failed to notify conversation.end:", e);
415
+ }
416
+ // Clean up conversation from active conversations
417
+ try {
418
+ console_1.console.log(`[ClaudeManager] Removing conversation from active map:`, {
419
+ conversationId: context.conversationId,
420
+ agentSessionId: context.agentSessionId,
421
+ mapSizeBefore: this.runner.activeConversations_.size,
422
+ });
423
+ this.runner.activeConversations_.delete(context.conversationId);
424
+ status_line_1.statusLineManager.updateActiveCount(this.runner.activeConversations_.size);
425
+ }
426
+ catch { }
427
+ }
428
+ async sendUserMessage(conversationId, content, config, conversationObjectType, conversationObjectId, conversation) {
429
+ console_1.console.log(`[ClaudeManager] sendUserMessage called with:`, {
430
+ conversationId,
431
+ conversationObjectType,
432
+ conversationObjectId,
433
+ hasConfig: !!config,
434
+ hasConversation: !!conversation,
435
+ activeConversations: this.runner.activeConversations_.size,
436
+ });
437
+ // Find by conversationId only
438
+ let context = this.runner.getConversationContext(conversationId);
439
+ console_1.console.log(`[ClaudeManager] Lookup by conversationId result:`, {
440
+ found: !!context,
441
+ conversationId: context?.conversationId,
442
+ agentSessionId: context?.agentSessionId,
443
+ });
444
+ if (!context && conversation) {
445
+ // Use provided conversation details
446
+ try {
447
+ const conversationDetails = conversation;
448
+ console_1.console.log(`[ClaudeManager] Using provided config from RunnerMessage:`, {
449
+ hasConfig: !!config,
450
+ hasRepository: !!config?.repository,
451
+ repositoryType: config?.repository?.type,
452
+ repositoryPath: config?.repository?.localPath,
453
+ hasTokens: !!(config?.accessToken || config?.githubToken),
454
+ hasMcpServers: !!config?.mcpServers,
455
+ });
456
+ // Use the config that was already prepared by TaskOrchestrator and sent in the RunnerMessage
457
+ const startConfig = {
458
+ anthropicApiKey: config?.anthropicApiKey || process.env["ANTHROPIC_API_KEY"] || "",
459
+ systemPrompt: config?.systemPrompt,
460
+ workspaceId: conversationDetails.workspaceId,
461
+ ...config, // Use the full config provided in the RunnerMessage
462
+ ...(conversationDetails.agentSessionId
463
+ ? { sessionId: conversationDetails.agentSessionId }
464
+ : {}),
465
+ };
466
+ // Start the SDK conversation (no initial messages); this attaches to existing session when provided
467
+ await this.startConversation(conversationDetails.objectType, conversationDetails.objectId, startConfig, [], conversationDetails);
468
+ // Refresh context after start/resume
469
+ context = this.runner.getConversationContext(conversationId);
470
+ }
471
+ catch (error) {
472
+ console_1.console.error(`Failed to fetch conversation ${conversationId}:`, error);
473
+ }
474
+ }
475
+ if (!context) {
476
+ throw new Error(`No active or fetchable conversation found for ${conversationId}`);
477
+ }
478
+ try {
479
+ // Send immediately when a conversation instance exists; no need to wait for "active"
480
+ if (!context.conversation) {
481
+ throw new Error(`No conversation instance found for conversation ${context.conversationId}`);
482
+ }
483
+ // Guard: Don't send messages if conversation is stopped or stopping
484
+ const conversationStatus = context.status;
485
+ if (conversationStatus === "stopped" ||
486
+ conversationStatus === "stopping") {
487
+ console_1.console.warn(`Attempted to send message to stopped/stopping conversation ${context.conversationId}`, {
488
+ status: context.status,
489
+ conversationObjectType: context.conversationObjectType,
490
+ conversationObjectId: context.conversationObjectId,
491
+ });
492
+ return;
493
+ }
494
+ // Native message injection - SDK handles queueing and delivery
495
+ const normalizedText = this.normalizeToText(content);
496
+ if (process.env["DEBUG"] === "true") {
497
+ console_1.console.log("[ClaudeManager] Normalized follow-up content", {
498
+ originalType: typeof content,
499
+ isArray: Array.isArray(content) || undefined,
500
+ normalizedPreview: typeof normalizedText === "string"
501
+ ? normalizedText.slice(0, 160)
502
+ : String(normalizedText).slice(0, 160),
503
+ });
504
+ }
505
+ context.conversation.send({
506
+ type: "text",
507
+ text: normalizedText,
508
+ });
509
+ // Update last activity timestamp
510
+ context.lastActivityAt = new Date();
511
+ console_1.console.log(`Sent user message to conversation ${context.conversationId} (agentSessionId: ${context.agentSessionId}, status: ${context.status})`);
512
+ }
513
+ catch (error) {
514
+ // Handle errors properly
515
+ await this._handleConversationError(context, error);
516
+ throw error; // Re-throw to maintain the same behavior
517
+ }
518
+ }
519
+ async fetchGithubTokens(workspaceId) {
520
+ try {
521
+ const response = await fetch(`${this.runner.config_.orchestratorUrl}/api/runner/tokens?workspaceId=${workspaceId}`, {
522
+ method: "GET",
523
+ headers: {
524
+ Authorization: `Bearer ${process.env["NORTHFLARE_RUNNER_TOKEN"]}`,
525
+ },
526
+ });
527
+ if (!response.ok) {
528
+ console_1.console.error(`Failed to fetch GitHub tokens: ${response.status}`);
529
+ return undefined;
530
+ }
531
+ const data = (await response.json());
532
+ return data.githubToken;
533
+ }
534
+ catch (error) {
535
+ console_1.console.error("Error fetching GitHub tokens:", error);
536
+ return undefined;
537
+ }
538
+ }
539
+ async _handleConversationError(context, error) {
540
+ const errorType = this.classifyError(error);
541
+ // Notify orchestrator
542
+ await this.runner.notify("error.report", {
543
+ conversationId: context.conversationId,
544
+ conversationObjectType: context.conversationObjectType,
545
+ conversationObjectId: context.conversationObjectId,
546
+ agentSessionId: context.agentSessionId,
547
+ errorType,
548
+ message: error.message,
549
+ details: {
550
+ stack: error.stack,
551
+ timestamp: new Date(),
552
+ },
553
+ });
554
+ // Conversation continues on error - no automatic cleanup
555
+ console_1.console.error(`Conversation error for ${context.conversationObjectType} ${context.conversationObjectId}:`, error);
556
+ }
557
+ classifyError(error) {
558
+ if (error.message.includes("process exited")) {
559
+ return "process_exit";
560
+ }
561
+ else if (error.message.includes("model")) {
562
+ return "model_error";
563
+ }
564
+ else if (error.message.includes("tool")) {
565
+ return "tool_error";
566
+ }
567
+ else if (error.message.includes("permission")) {
568
+ return "permission_error";
569
+ }
570
+ else if (error.message.includes("timeout")) {
571
+ return "timeout_error";
572
+ }
573
+ return "unknown_error";
574
+ }
575
+ /**
576
+ * Normalize arbitrary content shapes into a plain string for the CLI
577
+ */
578
+ normalizeToText(value) {
579
+ if (typeof value === "string")
580
+ return value;
581
+ if (value == null)
582
+ return "";
583
+ if (typeof value === "object") {
584
+ // Common simple shapes
585
+ if (typeof value.text === "string")
586
+ return value.text;
587
+ if (typeof value.text === "object" &&
588
+ value.text &&
589
+ typeof value.text.text === "string")
590
+ return value.text.text;
591
+ if (typeof value.content === "string")
592
+ return value.content;
593
+ // Array of blocks: [{ type: 'text', text: '...' }, ...]
594
+ if (Array.isArray(value)) {
595
+ const texts = value
596
+ .map((b) => b && b.type === "text" && typeof b.text === "string" ? b.text : null)
597
+ .filter((t) => !!t);
598
+ if (texts.length)
599
+ return texts.join(" ");
600
+ }
601
+ // Nested message shapes
602
+ if (typeof value.message === "object" &&
603
+ value.message &&
604
+ typeof value.message.text === "string") {
605
+ return value.message.text;
606
+ }
607
+ }
608
+ try {
609
+ return JSON.stringify(value);
610
+ }
611
+ catch {
612
+ return String(value);
613
+ }
614
+ }
615
+ async handleStreamedMessage(context, message, sessionId) {
616
+ /*
617
+ * SDK tool call payload reference (observed shapes)
618
+ *
619
+ * 1) Assistant tool call (tool_use)
620
+ * {
621
+ * "type": "assistant",
622
+ * "message": {
623
+ * "role": "assistant",
624
+ * "content": [
625
+ * { "type": "text", "text": "…optional text…" },
626
+ * { "type": "tool_use", "id": "toolu_01Nbv…", "name": "TodoWrite", "input": { ... } }
627
+ * ]
628
+ * },
629
+ * "session_id": "…"
630
+ * }
631
+ *
632
+ * 2) Tool result (often emitted as type 'user')
633
+ * {
634
+ * "type": "user",
635
+ * "message": {
636
+ * "role": "user",
637
+ * "content": [
638
+ * {
639
+ * "tool_use_id": "toolu_01E9R475…",
640
+ * "type": "tool_result",
641
+ * "content": "Todos have been modified successfully. …"
642
+ * }
643
+ * ]
644
+ * },
645
+ * "parent_tool_use_id": null,
646
+ * "session_id": "…",
647
+ * "uuid": "…"
648
+ * }
649
+ *
650
+ * Normalization (runner → server message.agent):
651
+ * - assistant tool_use → type: 'assistant', content: [{ toolCalls: [{ id, name, arguments }] }, …]
652
+ * - tool_result (any shape) → type: 'tool_result', subtype: 'tool_result', content:
653
+ * [ { type: 'tool_result', subtype: 'tool_result', tool_use_id, content } ]
654
+ */
655
+ // Guard: Don't process messages if conversation is stopped or stopping
656
+ const status = context.status;
657
+ if (status === "stopped" || status === "stopping") {
658
+ console_1.console.log(`Ignoring message for stopped/stopping conversation ${context.conversationId}`, {
659
+ status: context.status,
660
+ messageType: message.type,
661
+ });
662
+ return;
663
+ }
664
+ try {
665
+ // High-level receipt log
666
+ console_1.console.log(`Received streamed message for ${context.conversationObjectType} ${context.conversationObjectId}`, {
667
+ type: message?.type,
668
+ });
669
+ // Raw SDK message diagnostics
670
+ try {
671
+ // Log the full raw SDK message safely (handles circular refs)
672
+ const safeStringify = (obj) => {
673
+ const seen = new WeakSet();
674
+ return JSON.stringify(obj, (key, value) => {
675
+ if (typeof value === "function")
676
+ return undefined;
677
+ if (typeof value === "bigint")
678
+ return String(value);
679
+ if (typeof value === "object" && value !== null) {
680
+ if (seen.has(value))
681
+ return "[Circular]";
682
+ seen.add(value);
683
+ }
684
+ return value;
685
+ }, 2);
686
+ };
687
+ console_1.console.log("[ClaudeManager] RAW SDK message FULL:", safeStringify(message));
688
+ const summary = {
689
+ keys: Object.keys(message || {}),
690
+ hasMessage: !!message?.message,
691
+ contentType: typeof message?.content,
692
+ messageContentType: typeof message?.message?.content,
693
+ sessionId: message?.session_id || message?.sessionId || null,
694
+ };
695
+ console_1.console.log("[ClaudeManager] RAW SDK message summary:", summary);
696
+ if (message?.content !== undefined) {
697
+ console_1.console.log("[ClaudeManager] RAW SDK content:", message.content);
698
+ }
699
+ if (message?.message?.content !== undefined) {
700
+ console_1.console.log("[ClaudeManager] RAW SDK nested content:", message.message.content);
701
+ }
702
+ }
703
+ catch (e) {
704
+ console_1.console.warn("[ClaudeManager] Failed to log raw SDK message:", e);
705
+ }
706
+ // Build structured content based on message type
707
+ let messageType = message.type;
708
+ let subtype;
709
+ let structuredContent = {};
710
+ let isError = false;
711
+ let skipSend = false;
712
+ // Extract content based on message type
713
+ switch (message.type) {
714
+ case "assistant": {
715
+ const assistantMsg = message;
716
+ const blocks = assistantMsg?.message?.content || assistantMsg?.content || [];
717
+ const textContent = Array.isArray(blocks)
718
+ ? blocks
719
+ .filter((b) => b && b.type === "text" && typeof b.text === "string")
720
+ .map((b) => b.text)
721
+ .join("")
722
+ : "";
723
+ const toolCalls = Array.isArray(blocks)
724
+ ? blocks
725
+ .filter((b) => b && b.type === "tool_use")
726
+ .map((b) => ({
727
+ id: b.id,
728
+ name: b.name,
729
+ arguments: b.input,
730
+ }))
731
+ : undefined;
732
+ structuredContent = {
733
+ ...(textContent ? { text: textContent } : {}),
734
+ ...(toolCalls && toolCalls.length ? { toolCalls } : {}),
735
+ timestamp: new Date().toISOString(),
736
+ };
737
+ break;
738
+ }
739
+ case "thinking": {
740
+ // Map thinking to assistant with subtype
741
+ messageType = "assistant";
742
+ subtype = "thinking";
743
+ const thinkingMsg = message;
744
+ structuredContent = {
745
+ text: thinkingMsg.content || "",
746
+ timestamp: new Date().toISOString(),
747
+ };
748
+ break;
749
+ }
750
+ case "tool_use": {
751
+ // Tool call request - map to assistant
752
+ messageType = "assistant";
753
+ subtype = "tool_use";
754
+ const toolUseMsg = message;
755
+ structuredContent = {
756
+ toolCalls: [
757
+ {
758
+ id: toolUseMsg.id,
759
+ name: toolUseMsg.name,
760
+ arguments: toolUseMsg.input,
761
+ },
762
+ ],
763
+ timestamp: new Date().toISOString(),
764
+ };
765
+ break;
766
+ }
767
+ case "tool_result": {
768
+ // Tool execution result - normalize to v1-style tool_result blocks
769
+ // so the server persists as messageType: tool_call_result with correct content
770
+ messageType = "tool_result";
771
+ subtype = "tool_result";
772
+ const toolResultMsg = message;
773
+ structuredContent = [
774
+ {
775
+ type: "tool_result",
776
+ subtype: "tool_result",
777
+ tool_use_id: toolResultMsg.tool_use_id,
778
+ content: toolResultMsg.content, // Keep content as native (array or string)
779
+ timestamp: new Date().toISOString(),
780
+ },
781
+ ];
782
+ break;
783
+ }
784
+ case "result": {
785
+ const resultMsg = message;
786
+ structuredContent = {
787
+ text: resultMsg.content || resultMsg.result || "",
788
+ timestamp: new Date().toISOString(),
789
+ };
790
+ break;
791
+ }
792
+ case "user": {
793
+ const userMsg = message;
794
+ // Prefer nested message.content if present (SDK shape), fallback to top-level content
795
+ const blocks = (userMsg && userMsg.message && userMsg.message.content) ||
796
+ userMsg?.content ||
797
+ [];
798
+ if (Array.isArray(blocks)) {
799
+ const hasToolResult = blocks.some((b) => b && typeof b === "object" && b.type === "tool_result");
800
+ if (hasToolResult) {
801
+ // Normalize tool_result blocks to v1-style
802
+ messageType = "tool_result";
803
+ subtype = "tool_result";
804
+ structuredContent = blocks
805
+ .filter((b) => b && b.type === "tool_result")
806
+ .map((b) => ({
807
+ type: "tool_result",
808
+ subtype: "tool_result",
809
+ tool_use_id: b.tool_use_id || b.toolUseId || b.id,
810
+ content: b.content, // Keep content as native (array or string)
811
+ }));
812
+ }
813
+ else {
814
+ // Treat as plain text content by joining text blocks
815
+ const textContent = blocks
816
+ .filter((b) => b && b.type === "text" && typeof b.text === "string")
817
+ .map((b) => b.text)
818
+ .join("");
819
+ structuredContent = {
820
+ text: textContent,
821
+ timestamp: new Date().toISOString(),
822
+ };
823
+ }
824
+ // If content array only contains empty objects, skip sending
825
+ if (Array.isArray(structuredContent) &&
826
+ structuredContent.length > 0 &&
827
+ structuredContent.every((it) => !it || typeof it !== "object" || Object.keys(it).length === 0)) {
828
+ console_1.console.log("[ClaudeManager] Skipping empty 'user' message with only empty objects from SDK");
829
+ skipSend = true;
830
+ }
831
+ }
832
+ else if (typeof userMsg?.content === "string") {
833
+ // Attempt to parse JSON arrays (common for tool_result payloads)
834
+ const text = userMsg.content;
835
+ try {
836
+ const parsed = JSON.parse(text);
837
+ if (Array.isArray(parsed)) {
838
+ const hasToolResult = parsed.some((item) => item &&
839
+ typeof item === "object" &&
840
+ item.type === "tool_result");
841
+ if (hasToolResult) {
842
+ messageType = "tool_result";
843
+ subtype = "tool_result";
844
+ structuredContent = parsed;
845
+ }
846
+ else {
847
+ structuredContent = {
848
+ text,
849
+ timestamp: new Date().toISOString(),
850
+ };
851
+ }
852
+ }
853
+ else {
854
+ structuredContent = {
855
+ text,
856
+ timestamp: new Date().toISOString(),
857
+ };
858
+ }
859
+ }
860
+ catch {
861
+ // Not JSON - treat as plain text
862
+ structuredContent = { text, timestamp: new Date().toISOString() };
863
+ }
864
+ }
865
+ else {
866
+ // Other object content - preserve as is
867
+ structuredContent = userMsg?.content || {};
868
+ }
869
+ break;
870
+ }
871
+ case "system": {
872
+ const systemMsg = message;
873
+ const subtype = systemMsg.subtype || "system";
874
+ const model = systemMsg.model || systemMsg?.message?.model;
875
+ const permissionMode = systemMsg.permissionMode || systemMsg?.message?.permissionMode;
876
+ const summary = [
877
+ subtype && `[${subtype}]`,
878
+ model && `model=${model}`,
879
+ permissionMode && `perm=${permissionMode}`,
880
+ ]
881
+ .filter(Boolean)
882
+ .join(" ");
883
+ structuredContent = {
884
+ text: summary || "",
885
+ timestamp: new Date().toISOString(),
886
+ };
887
+ break;
888
+ }
889
+ case "error": {
890
+ const errorMsg = message;
891
+ messageType = "system";
892
+ subtype = "error";
893
+ isError = true;
894
+ structuredContent = {
895
+ text: errorMsg.message || errorMsg.error || "Unknown error",
896
+ errorType: errorMsg.error_type || "unknown",
897
+ errorDetails: {
898
+ stack: errorMsg.stack,
899
+ code: errorMsg.code,
900
+ context: errorMsg,
901
+ },
902
+ timestamp: new Date().toISOString(),
903
+ };
904
+ break;
905
+ }
906
+ default: {
907
+ // Unknown message type - log and send as assistant
908
+ const unknownMsg = message;
909
+ console_1.console.warn(`Unknown message type: ${unknownMsg.type}`, message);
910
+ messageType = "assistant";
911
+ structuredContent = {
912
+ text: JSON.stringify(message),
913
+ timestamp: new Date().toISOString(),
914
+ };
915
+ }
916
+ }
917
+ // Generate a unique message ID
918
+ const messageId = `${context.agentSessionId}-${Date.now()}-${Math.random()
919
+ .toString(36)
920
+ .substr(2, 9)}`;
921
+ // Send agent message to orchestrator with structured content
922
+ // Skip if conversation is stopping/stopped to avoid race conditions
923
+ const currentStatus = context.status;
924
+ if (currentStatus !== "stopped" && currentStatus !== "stopping") {
925
+ if (skipSend) {
926
+ console_1.console.log("[ClaudeManager] Not sending message.agent due to skipSend=true");
927
+ return;
928
+ }
929
+ const payload = {
930
+ conversationId: context.conversationId,
931
+ conversationObjectType: context.conversationObjectType,
932
+ conversationObjectId: context.conversationObjectId,
933
+ agentSessionId: context.agentSessionId,
934
+ type: messageType,
935
+ subtype,
936
+ content: Array.isArray(structuredContent)
937
+ ? structuredContent
938
+ : [structuredContent],
939
+ messageId,
940
+ isError,
941
+ };
942
+ try {
943
+ console_1.console.log("[ClaudeManager] Sending message.agent payload:", {
944
+ type: payload.type,
945
+ subtype: payload.subtype,
946
+ contentPreview: Array.isArray(payload.content)
947
+ ? payload.content.slice(0, 1)
948
+ : payload.content,
949
+ });
950
+ }
951
+ catch { }
952
+ await this.runner.notify("message.agent", payload);
953
+ }
954
+ // Tool calls are now handled directly by Claude through the MCP server
955
+ // We just log that we saw them but don't intercept or process them
956
+ if (structuredContent.toolCalls &&
957
+ structuredContent.toolCalls.length > 0) {
958
+ console_1.console.log(`Claude is making ${structuredContent.toolCalls.length} tool call(s) via MCP`, {
959
+ conversationObjectId: context.conversationObjectId,
960
+ toolNames: structuredContent.toolCalls.map((tc) => tc.name),
961
+ });
962
+ }
963
+ }
964
+ catch (error) {
965
+ // Check if this is a transport error due to stopped conversation
966
+ const errorMessage = error instanceof Error ? error.message : String(error);
967
+ const isTransportError = errorMessage.includes("Cannot read properties of undefined") ||
968
+ errorMessage.includes("stdout") ||
969
+ errorMessage.includes("transport");
970
+ const statusCheck = context.status;
971
+ if (isTransportError &&
972
+ (statusCheck === "stopped" || statusCheck === "stopping")) {
973
+ // This is expected when conversation is stopped - just log it
974
+ console_1.console.log(`Transport error for stopped/stopping conversation ${context.conversationId} (expected):`, errorMessage);
975
+ return;
976
+ }
977
+ console_1.console.error(`Error handling streamed message for ${context.conversationObjectType} ${context.conversationObjectId}:`, error);
978
+ await this._handleConversationError(context, error);
979
+ }
980
+ }
981
+ }
982
+ exports.ClaudeManager = ClaudeManager;
983
+ function createUserMessageStream() {
984
+ const queue = [];
985
+ let resolver = null;
986
+ let done = false;
987
+ async function* iterator() {
988
+ while (true) {
989
+ if (queue.length > 0) {
990
+ const value = queue.shift();
991
+ yield value;
992
+ continue;
993
+ }
994
+ if (done)
995
+ return;
996
+ await new Promise((resolve) => (resolver = resolve));
997
+ resolver = null;
998
+ }
999
+ }
1000
+ return {
1001
+ iterable: iterator(),
1002
+ enqueue: (msg) => {
1003
+ if (done)
1004
+ return;
1005
+ queue.push(msg);
1006
+ if (resolver) {
1007
+ const r = resolver;
1008
+ resolver = null;
1009
+ r();
1010
+ }
1011
+ },
1012
+ close: () => {
1013
+ done = true;
1014
+ if (resolver) {
1015
+ const r = resolver;
1016
+ resolver = null;
1017
+ r();
1018
+ }
1019
+ },
1020
+ };
1021
+ }
1022
+ function createConversationWrapper(sdk, input, onComplete) {
1023
+ let onSessionIdCb = null;
1024
+ let observedSessionId = null;
1025
+ let startedReader = false;
1026
+ function toSdkUserMessage(text) {
1027
+ return {
1028
+ type: "user",
1029
+ session_id: observedSessionId || "",
1030
+ parent_tool_use_id: null,
1031
+ message: {
1032
+ role: "user",
1033
+ content: text,
1034
+ },
1035
+ };
1036
+ }
1037
+ return {
1038
+ send(payload) {
1039
+ const text = payload?.text ?? "";
1040
+ input.enqueue(toSdkUserMessage(text));
1041
+ },
1042
+ async end() {
1043
+ try {
1044
+ input.close();
1045
+ }
1046
+ finally {
1047
+ // Simplified process cleanup
1048
+ try {
1049
+ if (sdk?.abortController) {
1050
+ sdk.abortController.abort();
1051
+ }
1052
+ }
1053
+ catch { }
1054
+ }
1055
+ },
1056
+ onSessionId(cb) {
1057
+ onSessionIdCb = cb;
1058
+ },
1059
+ stream(handler) {
1060
+ if (startedReader)
1061
+ return;
1062
+ startedReader = true;
1063
+ (async () => {
1064
+ try {
1065
+ for await (const msg of sdk) {
1066
+ // Simplified session ID extraction
1067
+ const sid = (msg && (msg.session_id || msg.sessionId)) || null;
1068
+ if (sid && sid !== observedSessionId) {
1069
+ observedSessionId = sid;
1070
+ if (onSessionIdCb)
1071
+ onSessionIdCb(sid);
1072
+ }
1073
+ await handler(msg, sid);
1074
+ }
1075
+ // Normal completion
1076
+ if (onComplete)
1077
+ await onComplete(false);
1078
+ }
1079
+ catch (e) {
1080
+ // Error completion
1081
+ if (onComplete)
1082
+ await onComplete(true, e);
1083
+ }
1084
+ })();
1085
+ },
1086
+ };
1087
+ }
1088
+ //# sourceMappingURL=claude-sdk-manager.js.map