@helmisatria/mcp-chrome-bridge 1.0.30

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 (129) hide show
  1. package/README.md +183 -0
  2. package/dist/README.md +25 -0
  3. package/dist/agent/attachment-service.d.ts +83 -0
  4. package/dist/agent/attachment-service.js +370 -0
  5. package/dist/agent/attachment-service.js.map +1 -0
  6. package/dist/agent/ccr-detector.d.ts +59 -0
  7. package/dist/agent/ccr-detector.js +311 -0
  8. package/dist/agent/ccr-detector.js.map +1 -0
  9. package/dist/agent/chat-service.d.ts +50 -0
  10. package/dist/agent/chat-service.js +439 -0
  11. package/dist/agent/chat-service.js.map +1 -0
  12. package/dist/agent/db/client.d.ts +26 -0
  13. package/dist/agent/db/client.js +244 -0
  14. package/dist/agent/db/client.js.map +1 -0
  15. package/dist/agent/db/index.d.ts +5 -0
  16. package/dist/agent/db/index.js +22 -0
  17. package/dist/agent/db/index.js.map +1 -0
  18. package/dist/agent/db/schema.d.ts +711 -0
  19. package/dist/agent/db/schema.js +121 -0
  20. package/dist/agent/db/schema.js.map +1 -0
  21. package/dist/agent/directory-picker.d.ts +11 -0
  22. package/dist/agent/directory-picker.js +149 -0
  23. package/dist/agent/directory-picker.js.map +1 -0
  24. package/dist/agent/engines/claude.d.ts +79 -0
  25. package/dist/agent/engines/claude.js +1338 -0
  26. package/dist/agent/engines/claude.js.map +1 -0
  27. package/dist/agent/engines/codex.d.ts +48 -0
  28. package/dist/agent/engines/codex.js +822 -0
  29. package/dist/agent/engines/codex.js.map +1 -0
  30. package/dist/agent/engines/types.d.ts +133 -0
  31. package/dist/agent/engines/types.js +3 -0
  32. package/dist/agent/engines/types.js.map +1 -0
  33. package/dist/agent/message-service.d.ts +56 -0
  34. package/dist/agent/message-service.js +198 -0
  35. package/dist/agent/message-service.js.map +1 -0
  36. package/dist/agent/open-project.d.ts +25 -0
  37. package/dist/agent/open-project.js +469 -0
  38. package/dist/agent/open-project.js.map +1 -0
  39. package/dist/agent/project-service.d.ts +49 -0
  40. package/dist/agent/project-service.js +254 -0
  41. package/dist/agent/project-service.js.map +1 -0
  42. package/dist/agent/project-types.d.ts +27 -0
  43. package/dist/agent/project-types.js +3 -0
  44. package/dist/agent/project-types.js.map +1 -0
  45. package/dist/agent/session-service.d.ts +198 -0
  46. package/dist/agent/session-service.js +292 -0
  47. package/dist/agent/session-service.js.map +1 -0
  48. package/dist/agent/storage.d.ts +27 -0
  49. package/dist/agent/storage.js +73 -0
  50. package/dist/agent/storage.js.map +1 -0
  51. package/dist/agent/stream-manager.d.ts +42 -0
  52. package/dist/agent/stream-manager.js +243 -0
  53. package/dist/agent/stream-manager.js.map +1 -0
  54. package/dist/agent/tool-bridge.d.ts +44 -0
  55. package/dist/agent/tool-bridge.js +50 -0
  56. package/dist/agent/tool-bridge.js.map +1 -0
  57. package/dist/agent/types.d.ts +6 -0
  58. package/dist/agent/types.js +3 -0
  59. package/dist/agent/types.js.map +1 -0
  60. package/dist/cli.d.ts +2 -0
  61. package/dist/cli.js +224 -0
  62. package/dist/cli.js.map +1 -0
  63. package/dist/constant/index.d.ts +60 -0
  64. package/dist/constant/index.js +80 -0
  65. package/dist/constant/index.js.map +1 -0
  66. package/dist/file-handler.d.ts +41 -0
  67. package/dist/file-handler.js +295 -0
  68. package/dist/file-handler.js.map +1 -0
  69. package/dist/index.d.ts +2 -0
  70. package/dist/index.js +35 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/mcp/mcp-server-stdio.d.ts +72 -0
  73. package/dist/mcp/mcp-server-stdio.js +143 -0
  74. package/dist/mcp/mcp-server-stdio.js.map +1 -0
  75. package/dist/mcp/mcp-server.d.ts +36 -0
  76. package/dist/mcp/mcp-server.js +26 -0
  77. package/dist/mcp/mcp-server.js.map +1 -0
  78. package/dist/mcp/register-tools.d.ts +2 -0
  79. package/dist/mcp/register-tools.js +148 -0
  80. package/dist/mcp/register-tools.js.map +1 -0
  81. package/dist/mcp/stdio-config.json +3 -0
  82. package/dist/native-messaging-host.d.ts +42 -0
  83. package/dist/native-messaging-host.js +312 -0
  84. package/dist/native-messaging-host.js.map +1 -0
  85. package/dist/run_host.bat +194 -0
  86. package/dist/run_host.sh +264 -0
  87. package/dist/scripts/browser-config.d.ts +28 -0
  88. package/dist/scripts/browser-config.js +229 -0
  89. package/dist/scripts/browser-config.js.map +1 -0
  90. package/dist/scripts/build.d.ts +1 -0
  91. package/dist/scripts/build.js +126 -0
  92. package/dist/scripts/build.js.map +1 -0
  93. package/dist/scripts/constant.d.ts +4 -0
  94. package/dist/scripts/constant.js +8 -0
  95. package/dist/scripts/constant.js.map +1 -0
  96. package/dist/scripts/doctor.d.ts +70 -0
  97. package/dist/scripts/doctor.js +930 -0
  98. package/dist/scripts/doctor.js.map +1 -0
  99. package/dist/scripts/postinstall.d.ts +2 -0
  100. package/dist/scripts/postinstall.js +246 -0
  101. package/dist/scripts/postinstall.js.map +1 -0
  102. package/dist/scripts/register-dev.d.ts +1 -0
  103. package/dist/scripts/register-dev.js +5 -0
  104. package/dist/scripts/register-dev.js.map +1 -0
  105. package/dist/scripts/register.d.ts +2 -0
  106. package/dist/scripts/register.js +28 -0
  107. package/dist/scripts/register.js.map +1 -0
  108. package/dist/scripts/report.d.ts +96 -0
  109. package/dist/scripts/report.js +686 -0
  110. package/dist/scripts/report.js.map +1 -0
  111. package/dist/scripts/utils.d.ts +64 -0
  112. package/dist/scripts/utils.js +443 -0
  113. package/dist/scripts/utils.js.map +1 -0
  114. package/dist/server/index.d.ts +35 -0
  115. package/dist/server/index.js +312 -0
  116. package/dist/server/index.js.map +1 -0
  117. package/dist/server/routes/agent.d.ts +21 -0
  118. package/dist/server/routes/agent.js +971 -0
  119. package/dist/server/routes/agent.js.map +1 -0
  120. package/dist/server/routes/index.d.ts +4 -0
  121. package/dist/server/routes/index.js +9 -0
  122. package/dist/server/routes/index.js.map +1 -0
  123. package/dist/trace-analyzer.d.ts +14 -0
  124. package/dist/trace-analyzer.js +113 -0
  125. package/dist/trace-analyzer.js.map +1 -0
  126. package/dist/util/logger.d.ts +1 -0
  127. package/dist/util/logger.js +43 -0
  128. package/dist/util/logger.js.map +1 -0
  129. package/package.json +91 -0
@@ -0,0 +1,822 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CodexEngine = void 0;
7
+ const node_child_process_1 = require("node:child_process");
8
+ const node_readline_1 = __importDefault(require("node:readline"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_crypto_1 = require("node:crypto");
11
+ const chrome_mcp_shared_1 = require("chrome-mcp-shared");
12
+ const tool_bridge_1 = require("../tool-bridge");
13
+ const project_service_1 = require("../project-service");
14
+ const constant_1 = require("../../constant");
15
+ /**
16
+ * CodexEngine integrates the Codex CLI as an AgentEngine implementation.
17
+ *
18
+ * The implementation is intentionally self-contained and does not persist messages;
19
+ * it focuses on streaming Codex JSON events into RealtimeEvent envelopes that the
20
+ * sidepanel UI can consume.
21
+ *
22
+ * 中文说明:该引擎基于 other/cweb 中 Codex 适配器的事件协议,完整处理
23
+ * item.started/item.delta/item.completed/item.failed/error 等事件,并
24
+ * 通过 AgentStreamManager 将编码后的 RealtimeEvent 推送给 sidepanel,
25
+ * 确保数据链路「Sidepanel → Native Server → Codex CLI → Sidepanel」闭环。
26
+ */
27
+ class CodexEngine {
28
+ constructor(toolBridge) {
29
+ this.name = 'codex';
30
+ this.supportsMcp = false;
31
+ this.toolBridge = toolBridge !== null && toolBridge !== void 0 ? toolBridge : new tool_bridge_1.AgentToolBridge();
32
+ }
33
+ async initializeAndRun(options, ctx) {
34
+ var _a;
35
+ const { sessionId, instruction, model, projectRoot, projectId, requestId, signal, attachments, resolvedImagePaths, codexConfig, } = options;
36
+ const repoPath = this.resolveRepoPath(projectRoot);
37
+ // Check if already aborted
38
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
39
+ throw new Error('CodexEngine: execution was cancelled');
40
+ }
41
+ const normalizedInstruction = instruction.trim();
42
+ if (!normalizedInstruction) {
43
+ throw new Error('CodexEngine: instruction must not be empty');
44
+ }
45
+ // Merge user config with defaults
46
+ const resolvedConfig = {
47
+ ...chrome_mcp_shared_1.DEFAULT_CODEX_CONFIG,
48
+ ...(codexConfig !== null && codexConfig !== void 0 ? codexConfig : {}),
49
+ };
50
+ // Ensure autoInstructions has a value
51
+ if (!((_a = resolvedConfig.autoInstructions) === null || _a === void 0 ? void 0 : _a.trim())) {
52
+ resolvedConfig.autoInstructions = chrome_mcp_shared_1.CODEX_AUTO_INSTRUCTIONS;
53
+ }
54
+ // Resolve project-scoped Chrome MCP toggle (default: enabled)
55
+ const enableChromeMcp = await (async () => {
56
+ if (!projectId)
57
+ return true;
58
+ try {
59
+ const project = await (0, project_service_1.getProject)(projectId);
60
+ return (project === null || project === void 0 ? void 0 : project.enableChromeMcp) !== false;
61
+ }
62
+ catch (err) {
63
+ const message = err instanceof Error ? err.message : String(err);
64
+ console.error(`[CodexEngine] Failed to load project enableChromeMcp, defaulting to enabled: ${message}`);
65
+ return true;
66
+ }
67
+ })();
68
+ // Optionally append project context to the prompt
69
+ const prompt = resolvedConfig.appendProjectContext
70
+ ? await this.appendProjectContext(normalizedInstruction, repoPath)
71
+ : normalizedInstruction;
72
+ const executable = process.platform === 'win32' ? 'codex.cmd' : 'codex';
73
+ const args = [
74
+ 'exec',
75
+ '--json',
76
+ '--skip-git-repo-check',
77
+ '--dangerously-bypass-approvals-and-sandbox',
78
+ '--color',
79
+ 'never',
80
+ '--cd',
81
+ repoPath,
82
+ ];
83
+ // Add Codex configuration arguments
84
+ args.push(...this.buildCodexConfigArgs(resolvedConfig));
85
+ // Inject local Chrome MCP server via runtime config override (no global codex config mutation)
86
+ // Use a unique server name to avoid collision with any existing global config
87
+ if (enableChromeMcp) {
88
+ const chromeMcpUrl = (0, constant_1.getChromeMcpUrl)();
89
+ // Set both url and type for complete HTTP MCP server configuration
90
+ args.push('-c', `mcp_servers.chrome_mcp_http.url=${JSON.stringify(chromeMcpUrl)}`);
91
+ args.push('-c', `mcp_servers.chrome_mcp_http.type="http"`);
92
+ console.error(`[CodexEngine] Chrome MCP server enabled: ${chromeMcpUrl}`);
93
+ }
94
+ else {
95
+ console.error('[CodexEngine] Chrome MCP server disabled');
96
+ }
97
+ if (model && model.trim()) {
98
+ args.push('--model', model.trim());
99
+ }
100
+ // Process image attachments - prefer resolvedImagePaths (persisted), fallback to temp files
101
+ const tempFiles = [];
102
+ const hasResolvedPaths = resolvedImagePaths && resolvedImagePaths.length > 0;
103
+ if (hasResolvedPaths) {
104
+ // Use pre-resolved persistent paths (preferred - no temp files needed)
105
+ console.error(`[CodexEngine] Using ${resolvedImagePaths.length} pre-resolved image path(s)`);
106
+ for (const imagePath of resolvedImagePaths) {
107
+ args.push('--image', imagePath);
108
+ }
109
+ }
110
+ else if (attachments && attachments.length > 0) {
111
+ // Fallback: write base64 to temp files (legacy behavior)
112
+ for (const attachment of attachments) {
113
+ if (attachment.type === 'image') {
114
+ try {
115
+ const tempFile = await this.writeAttachmentToTemp(attachment);
116
+ tempFiles.push(tempFile);
117
+ args.push('--image', tempFile);
118
+ }
119
+ catch (err) {
120
+ console.error('[CodexEngine] Failed to write attachment to temp file:', err);
121
+ }
122
+ }
123
+ }
124
+ }
125
+ args.push(prompt);
126
+ // Use explicit Promise wrapping to ensure child process errors are properly rejected.
127
+ return new Promise((resolve, reject) => {
128
+ var _a, _b;
129
+ const child = (0, node_child_process_1.spawn)(executable, args, {
130
+ cwd: repoPath,
131
+ env: this.buildCodexEnv(),
132
+ stdio: ['ignore', 'pipe', 'pipe'],
133
+ });
134
+ // State management
135
+ const stderrBuffer = [];
136
+ let hasCompleted = false;
137
+ let timedOut = false;
138
+ let settled = false;
139
+ let timeoutHandle = null;
140
+ // Readline interface - declared early to avoid TDZ issues in finish()
141
+ let rl = null;
142
+ // Assistant message state
143
+ let assistantBuffer = '';
144
+ let assistantMessageId = null;
145
+ let assistantCreatedAt = null;
146
+ const streamedToolHashes = new Set();
147
+ const activeCommands = new Map();
148
+ const thinkingSegments = [];
149
+ /**
150
+ * Cleanup temporary files created for image attachments.
151
+ */
152
+ const cleanupTempFiles = async () => {
153
+ if (tempFiles.length === 0)
154
+ return;
155
+ const fs = await import('node:fs/promises');
156
+ for (const filePath of tempFiles) {
157
+ try {
158
+ await fs.unlink(filePath);
159
+ console.error(`[CodexEngine] Cleaned up temp file: ${filePath}`);
160
+ }
161
+ catch (err) {
162
+ // Ignore errors during cleanup - file may already be deleted
163
+ console.error(`[CodexEngine] Failed to cleanup temp file ${filePath}:`, err);
164
+ }
165
+ }
166
+ };
167
+ /**
168
+ * Cleanup and settle the promise (resolve or reject).
169
+ * Waits for temp file cleanup to complete before settling.
170
+ */
171
+ const finish = async (error) => {
172
+ if (settled)
173
+ return;
174
+ settled = true;
175
+ // Clear timeout
176
+ if (timeoutHandle) {
177
+ clearTimeout(timeoutHandle);
178
+ timeoutHandle = null;
179
+ }
180
+ // Close readline interface
181
+ if (rl) {
182
+ try {
183
+ rl.close();
184
+ }
185
+ catch (_a) {
186
+ // Ignore close errors during cleanup
187
+ }
188
+ }
189
+ // Kill child process if still running
190
+ if (!child.killed) {
191
+ try {
192
+ child.kill();
193
+ }
194
+ catch (_b) {
195
+ // Ignore kill errors during cleanup
196
+ }
197
+ }
198
+ // Cleanup temp files after process is killed (wait for completion)
199
+ await cleanupTempFiles();
200
+ // Settle the promise
201
+ if (error) {
202
+ reject(error instanceof Error ? error : new Error(String(error)));
203
+ }
204
+ else {
205
+ resolve();
206
+ }
207
+ };
208
+ // Handle child process error immediately after spawn (e.g., command not found)
209
+ child.on('error', (error) => {
210
+ const message = error instanceof Error
211
+ ? error.message
212
+ : stderrBuffer.slice(-5).join('\n') || 'Codex CLI failed to start';
213
+ void finish(new Error(`CodexEngine: ${message}`));
214
+ });
215
+ // Listen for abort signal to cancel execution
216
+ const abortHandler = signal
217
+ ? () => {
218
+ console.error('[CodexEngine] Execution cancelled via abort signal');
219
+ void finish(new Error('CodexEngine: execution was cancelled'));
220
+ }
221
+ : null;
222
+ if (signal && abortHandler) {
223
+ signal.addEventListener('abort', abortHandler, { once: true });
224
+ }
225
+ // Collect stderr with bounded buffer
226
+ (_a = child.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (chunk) => {
227
+ const text = String(chunk).trim();
228
+ if (!text)
229
+ return;
230
+ stderrBuffer.push(text);
231
+ // Keep only the most recent lines to prevent memory growth
232
+ if (stderrBuffer.length > CodexEngine.MAX_STDERR_LINES) {
233
+ stderrBuffer.splice(0, stderrBuffer.length - CodexEngine.MAX_STDERR_LINES);
234
+ }
235
+ console.error('[CodexEngine][stderr]', text);
236
+ });
237
+ rl = node_readline_1.default.createInterface({ input: child.stdout });
238
+ /**
239
+ * Build the assistant message payload, combining thinking and agent content.
240
+ */
241
+ const buildAssistantPayload = () => {
242
+ const trimmedAssistant = assistantBuffer.trim();
243
+ const thinkingContent = thinkingSegments
244
+ .map((segment) => segment.trim())
245
+ .filter((segment) => segment.length > 0)
246
+ .map((segment) => `<thinking>${segment}</thinking>`)
247
+ .join('\n\n');
248
+ const parts = [];
249
+ if (thinkingContent) {
250
+ parts.push(thinkingContent);
251
+ }
252
+ if (trimmedAssistant) {
253
+ parts.push(trimmedAssistant);
254
+ }
255
+ return parts.join('\n\n').trim();
256
+ };
257
+ /**
258
+ * Reset assistant buffers after emitting a final message.
259
+ */
260
+ const resetAssistantBuffers = () => {
261
+ assistantBuffer = '';
262
+ thinkingSegments.length = 0;
263
+ assistantMessageId = null;
264
+ assistantCreatedAt = null;
265
+ };
266
+ // Helper: emit assistant message
267
+ const emitAssistant = (isFinal) => {
268
+ const content = buildAssistantPayload();
269
+ if (!content)
270
+ return;
271
+ if (!assistantMessageId) {
272
+ assistantMessageId = (0, node_crypto_1.randomUUID)();
273
+ }
274
+ if (!assistantCreatedAt) {
275
+ assistantCreatedAt = new Date().toISOString();
276
+ }
277
+ const message = {
278
+ id: assistantMessageId,
279
+ sessionId,
280
+ role: 'assistant',
281
+ content,
282
+ messageType: 'chat',
283
+ cliSource: this.name,
284
+ requestId,
285
+ isStreaming: !isFinal,
286
+ isFinal,
287
+ createdAt: assistantCreatedAt,
288
+ };
289
+ ctx.emit({ type: 'message', data: message });
290
+ };
291
+ // Helper: emit tool message with deduplication
292
+ const dispatchToolMessage = (content, metadata, messageType, isStreaming) => {
293
+ const trimmed = content.trim();
294
+ if (!trimmed)
295
+ return;
296
+ const hash = this.encodeHash(`${messageType}:${trimmed}:${JSON.stringify(metadata)}:${sessionId}:${requestId || ''}`).slice(0, 16);
297
+ if (streamedToolHashes.has(hash))
298
+ return;
299
+ streamedToolHashes.add(hash);
300
+ const message = {
301
+ id: (0, node_crypto_1.randomUUID)(),
302
+ sessionId,
303
+ role: 'tool',
304
+ content: trimmed,
305
+ messageType,
306
+ cliSource: this.name,
307
+ requestId,
308
+ isStreaming,
309
+ isFinal: !isStreaming,
310
+ createdAt: new Date().toISOString(),
311
+ metadata: { cli_type: 'codex', ...metadata },
312
+ };
313
+ ctx.emit({ type: 'message', data: message });
314
+ };
315
+ // Event handlers for specific item types
316
+ const emitCommandStart = (item) => {
317
+ var _a, _b;
318
+ const id = (_a = this.pickFirstString(item.id)) !== null && _a !== void 0 ? _a : (0, node_crypto_1.randomUUID)();
319
+ const command = this.pickFirstString(item.command);
320
+ activeCommands.set(id, { command });
321
+ dispatchToolMessage(command ? `Running: ${command}` : 'Running command', {
322
+ toolName: 'Bash',
323
+ tool_name: 'Bash',
324
+ command,
325
+ status: (_b = this.pickFirstString(item.status)) !== null && _b !== void 0 ? _b : 'in_progress',
326
+ }, 'tool_use', true);
327
+ };
328
+ const emitCommandResult = (item) => {
329
+ var _a, _b;
330
+ const id = this.pickFirstString(item.id);
331
+ const tracked = id ? activeCommands.get(id) : undefined;
332
+ if (id) {
333
+ activeCommands.delete(id);
334
+ }
335
+ const command = (_a = this.pickFirstString(item.command)) !== null && _a !== void 0 ? _a : tracked === null || tracked === void 0 ? void 0 : tracked.command;
336
+ const output = (_b = this.pickFirstString(item.aggregated_output)) !== null && _b !== void 0 ? _b : '';
337
+ const exitCode = typeof item.exit_code === 'number' ? item.exit_code : undefined;
338
+ const status = this.pickFirstString(item.status);
339
+ const isError = status === 'failed' || (typeof exitCode === 'number' && exitCode !== 0);
340
+ const summary = command ? `Ran: ${command}` : 'Executed shell command';
341
+ const exitSuffix = typeof exitCode === 'number' ? ` (exit ${exitCode})` : '';
342
+ const body = output.trim();
343
+ const fullContent = body ? `${summary}${exitSuffix}\n\n${body}` : `${summary}${exitSuffix}`;
344
+ dispatchToolMessage(fullContent, {
345
+ toolName: 'Bash',
346
+ tool_name: 'Bash',
347
+ command,
348
+ exitCode,
349
+ status,
350
+ output,
351
+ is_error: isError || undefined,
352
+ }, 'tool_result', false);
353
+ };
354
+ const emitFileChange = (item) => {
355
+ var _a;
356
+ const { content, metadata } = this.summarizeApplyPatch({
357
+ changes: item.changes,
358
+ });
359
+ const status = (_a = this.pickFirstString(item.status)) !== null && _a !== void 0 ? _a : 'completed';
360
+ const isError = status === 'failed';
361
+ const toolName = (metadata === null || metadata === void 0 ? void 0 : metadata.toolName) || (metadata === null || metadata === void 0 ? void 0 : metadata.tool_name) || 'Edit';
362
+ dispatchToolMessage(isError ? `Failed: ${content}` : content, { ...metadata, toolName, tool_name: toolName, status, is_error: isError || undefined }, 'tool_result', false);
363
+ };
364
+ const emitTodoListUpdate = (record, phase) => {
365
+ var _a;
366
+ const rawItems = this.extractTodoListItems(record);
367
+ const items = this.normalizeTodoListItems(rawItems);
368
+ const content = this.buildTodoListContent(items, phase);
369
+ const status = (_a = this.pickFirstString(record.status)) !== null && _a !== void 0 ? _a : (phase === 'completed' ? 'completed' : 'in_progress');
370
+ const metadata = this.createTodoListMetadata(items, phase, {
371
+ status,
372
+ planId: this.pickFirstString(record.id),
373
+ });
374
+ dispatchToolMessage(content, metadata, phase === 'completed' ? 'tool_result' : 'tool_use', phase === 'update');
375
+ };
376
+ // Item event handlers
377
+ const handleItemStarted = (item) => {
378
+ if (!item || typeof item !== 'object')
379
+ return;
380
+ const record = item;
381
+ const type = this.pickFirstString(record.type);
382
+ if (type === 'command_execution') {
383
+ emitCommandStart(record);
384
+ }
385
+ else if (type === 'todo_list') {
386
+ emitTodoListUpdate(record, 'started');
387
+ }
388
+ };
389
+ const handleItemDelta = (delta) => {
390
+ if (!delta || typeof delta !== 'object')
391
+ return;
392
+ const record = delta;
393
+ const type = this.pickFirstString(record.type);
394
+ if (type === 'agent_message') {
395
+ const text = this.pickFirstString(record.text);
396
+ if (text) {
397
+ assistantBuffer += text;
398
+ emitAssistant(false);
399
+ }
400
+ }
401
+ else if (type === 'reasoning') {
402
+ const text = this.pickFirstString(record.text);
403
+ if (text) {
404
+ thinkingSegments.push(text);
405
+ emitAssistant(false);
406
+ }
407
+ }
408
+ else if (type === 'todo_list') {
409
+ emitTodoListUpdate(record, 'update');
410
+ }
411
+ };
412
+ const handleItemCompleted = (item) => {
413
+ if (!item || typeof item !== 'object')
414
+ return;
415
+ const record = item;
416
+ const type = this.pickFirstString(record.type);
417
+ switch (type) {
418
+ case 'command_execution':
419
+ emitCommandResult(record);
420
+ break;
421
+ case 'file_change':
422
+ emitFileChange(record);
423
+ break;
424
+ case 'todo_list':
425
+ emitTodoListUpdate(record, 'completed');
426
+ break;
427
+ case 'agent_message': {
428
+ const text = this.pickFirstString(record.text);
429
+ if (text)
430
+ assistantBuffer = text;
431
+ emitAssistant(true);
432
+ resetAssistantBuffers();
433
+ break;
434
+ }
435
+ case 'reasoning': {
436
+ const text = this.pickFirstString(record.text);
437
+ if (text) {
438
+ thinkingSegments.push(text);
439
+ emitAssistant(false);
440
+ }
441
+ break;
442
+ }
443
+ default: {
444
+ const text = this.pickFirstString(record.text);
445
+ if (text) {
446
+ thinkingSegments.push(text);
447
+ emitAssistant(false);
448
+ }
449
+ break;
450
+ }
451
+ }
452
+ };
453
+ // Setup timeout
454
+ const timeoutMs = Number.parseInt(process.env.CODEX_ENGINE_TIMEOUT_MS || '', 10) || 15 * 60 * 1000;
455
+ timeoutHandle = setTimeout(() => {
456
+ timedOut = true;
457
+ // Close readline to exit the loop
458
+ try {
459
+ rl.close();
460
+ }
461
+ catch (_a) {
462
+ // Ignore
463
+ }
464
+ if (!child.killed) {
465
+ try {
466
+ child.kill();
467
+ }
468
+ catch (_b) {
469
+ // Ignore
470
+ }
471
+ }
472
+ }, timeoutMs);
473
+ (_b = timeoutHandle.unref) === null || _b === void 0 ? void 0 : _b.call(timeoutHandle);
474
+ // Cleanup timeout and handle abnormal exit
475
+ child.on('close', (code, closeSignal) => {
476
+ if (timeoutHandle) {
477
+ clearTimeout(timeoutHandle);
478
+ timeoutHandle = null;
479
+ }
480
+ // If already timed out, settled, or completed normally, do nothing
481
+ if (timedOut || settled || hasCompleted) {
482
+ return;
483
+ }
484
+ // Build error detail from exit code and signal
485
+ const detailParts = [];
486
+ if (typeof code === 'number') {
487
+ detailParts.push(`exit code ${code}`);
488
+ }
489
+ if (closeSignal) {
490
+ detailParts.push(`signal ${closeSignal}`);
491
+ }
492
+ const detail = detailParts.length > 0 ? detailParts.join(', ') : 'unexpected shutdown';
493
+ // Emit final assistant message and mark as failed
494
+ emitAssistant(true);
495
+ resetAssistantBuffers();
496
+ hasCompleted = true;
497
+ void finish(new Error(`CodexEngine: process terminated (${detail})`));
498
+ });
499
+ // Main event processing loop (wrapped in IIFE to handle async properly)
500
+ void (async () => {
501
+ var _a, _b, _c, _d;
502
+ try {
503
+ for await (const line of rl) {
504
+ const trimmed = line.trim();
505
+ if (!trimmed)
506
+ continue;
507
+ let event;
508
+ try {
509
+ event = JSON.parse(trimmed);
510
+ }
511
+ catch (_e) {
512
+ console.warn('[CodexEngine] Failed to parse Codex event line:', trimmed);
513
+ continue;
514
+ }
515
+ const eventType = this.pickFirstString(event.type);
516
+ switch (eventType) {
517
+ case 'item.started':
518
+ handleItemStarted((_a = event.item) !== null && _a !== void 0 ? _a : null);
519
+ break;
520
+ case 'item.delta':
521
+ handleItemDelta((_b = event.delta) !== null && _b !== void 0 ? _b : null);
522
+ break;
523
+ case 'item.completed':
524
+ handleItemCompleted((_c = event.item) !== null && _c !== void 0 ? _c : null);
525
+ break;
526
+ case 'item.failed': {
527
+ const item = (_d = event.item) !== null && _d !== void 0 ? _d : null;
528
+ handleItemCompleted(item);
529
+ // Flush assistant message before throwing (aligned with other/cweb)
530
+ emitAssistant(true);
531
+ resetAssistantBuffers();
532
+ const msg = (item &&
533
+ typeof item === 'object' &&
534
+ this.pickFirstString(item.error)) ||
535
+ 'Codex execution failed';
536
+ hasCompleted = true;
537
+ throw new Error(msg);
538
+ }
539
+ case 'error': {
540
+ // Flush assistant message before throwing (aligned with other/cweb)
541
+ emitAssistant(true);
542
+ resetAssistantBuffers();
543
+ const msg = this.pickFirstString(event.error) ||
544
+ this.pickFirstString(event.message) ||
545
+ stderrBuffer.slice(-5).join('\n') ||
546
+ 'Codex execution error';
547
+ hasCompleted = true;
548
+ throw new Error(msg);
549
+ }
550
+ case 'turn.completed':
551
+ emitAssistant(true);
552
+ resetAssistantBuffers();
553
+ hasCompleted = true;
554
+ break;
555
+ default:
556
+ // Non-critical events are ignored
557
+ break;
558
+ }
559
+ }
560
+ // Check for timeout after loop exits
561
+ if (timedOut) {
562
+ throw new Error('CodexEngine: execution timed out');
563
+ }
564
+ // Emit final assistant message if not already completed
565
+ if (!hasCompleted) {
566
+ emitAssistant(true);
567
+ resetAssistantBuffers();
568
+ hasCompleted = true;
569
+ }
570
+ await finish();
571
+ }
572
+ catch (error) {
573
+ await finish(error);
574
+ }
575
+ })();
576
+ });
577
+ }
578
+ resolveRepoPath(projectRoot) {
579
+ const base = (projectRoot && projectRoot.trim()) || process.env.MCP_AGENT_PROJECT_ROOT || process.cwd();
580
+ return node_path_1.default.resolve(base);
581
+ }
582
+ /**
583
+ * Append project context (file listing) to the prompt.
584
+ * Aligned with other/cweb implementation.
585
+ */
586
+ async appendProjectContext(baseInstruction, repoPath) {
587
+ try {
588
+ const fs = await import('node:fs/promises');
589
+ const entries = await fs.readdir(repoPath, { withFileTypes: true });
590
+ const visible = entries
591
+ .filter((entry) => !entry.name.startsWith('.git') && entry.name !== 'AGENTS.md')
592
+ .map((entry) => entry.name);
593
+ if (visible.length === 0) {
594
+ return `${baseInstruction}
595
+
596
+ <current_project_context>
597
+ This is an empty project directory. Work directly in the current folder without creating extra subdirectories.
598
+ </current_project_context>`;
599
+ }
600
+ return `${baseInstruction}
601
+
602
+ <current_project_context>
603
+ Current files in project directory: ${visible.sort().join(', ')}
604
+ Work directly in the current directory. Do not create subdirectories unless specifically requested.
605
+ </current_project_context>`;
606
+ }
607
+ catch (error) {
608
+ console.warn('[CodexEngine] Failed to append project context:', error);
609
+ return baseInstruction;
610
+ }
611
+ }
612
+ /**
613
+ * Build Codex CLI configuration arguments from the resolved config.
614
+ * Aligned with other/cweb implementation for feature parity.
615
+ */
616
+ buildCodexConfigArgs(config) {
617
+ const args = [];
618
+ const pushConfig = (key, value) => {
619
+ args.push('-c', `${key}=${String(value)}`);
620
+ };
621
+ pushConfig('include_apply_patch_tool', config.includeApplyPatchTool);
622
+ pushConfig('include_plan_tool', config.includePlanTool);
623
+ pushConfig('tools.web_search_request', config.enableWebSearch);
624
+ pushConfig('use_experimental_streamable_shell_tool', config.useStreamableShell);
625
+ pushConfig('sandbox_mode', config.sandboxMode);
626
+ pushConfig('max_turns', config.maxTurns);
627
+ pushConfig('max_thinking_tokens', config.maxThinkingTokens);
628
+ pushConfig('reasoning_effort', config.reasoningEffort);
629
+ args.push('-c', `instructions=${JSON.stringify(config.autoInstructions)}`);
630
+ return args;
631
+ }
632
+ /**
633
+ * Write an attachment to a temporary file and return its path.
634
+ */
635
+ async writeAttachmentToTemp(attachment) {
636
+ const os = await import('node:os');
637
+ const fs = await import('node:fs/promises');
638
+ const tempDir = os.tmpdir();
639
+ const ext = attachment.mimeType.split('/')[1] || 'bin';
640
+ const sanitizedName = attachment.name.replace(/[^a-zA-Z0-9.-]/g, '_');
641
+ const fileName = `mcp-agent-${Date.now()}-${sanitizedName}.${ext}`;
642
+ const filePath = node_path_1.default.join(tempDir, fileName);
643
+ const buffer = Buffer.from(attachment.dataBase64, 'base64');
644
+ await fs.writeFile(filePath, buffer);
645
+ return filePath;
646
+ }
647
+ buildCodexEnv() {
648
+ const env = { ...process.env };
649
+ const extraPaths = [];
650
+ const globalPath = process.env.NPM_GLOBAL_PATH;
651
+ if (globalPath) {
652
+ extraPaths.push(globalPath);
653
+ }
654
+ // Enhanced Windows PATH handling (aligned with other/cweb)
655
+ if (process.platform === 'win32') {
656
+ const appData = process.env.APPDATA;
657
+ const localApp = process.env.LOCALAPPDATA;
658
+ if (appData) {
659
+ extraPaths.push(node_path_1.default.join(appData, 'npm'));
660
+ }
661
+ if (localApp) {
662
+ extraPaths.push(node_path_1.default.join(localApp, 'Programs', 'nodejs'));
663
+ }
664
+ }
665
+ if (extraPaths.length > 0) {
666
+ const currentPath = env.PATH || env.Path || '';
667
+ env.PATH = [...extraPaths, currentPath].filter(Boolean).join(node_path_1.default.delimiter);
668
+ }
669
+ return env;
670
+ }
671
+ pickFirstString(value) {
672
+ if (typeof value === 'string') {
673
+ const trimmed = value.trim();
674
+ return trimmed.length > 0 ? trimmed : undefined;
675
+ }
676
+ if (typeof value === 'number' || typeof value === 'boolean') {
677
+ return String(value);
678
+ }
679
+ if (Array.isArray(value)) {
680
+ for (const entry of value) {
681
+ const candidate = this.pickFirstString(entry);
682
+ if (candidate) {
683
+ return candidate;
684
+ }
685
+ }
686
+ return undefined;
687
+ }
688
+ if (value && typeof value === 'object') {
689
+ const record = value;
690
+ for (const key of Object.keys(record)) {
691
+ const candidate = this.pickFirstString(record[key]);
692
+ if (candidate) {
693
+ return candidate;
694
+ }
695
+ }
696
+ }
697
+ return undefined;
698
+ }
699
+ summarizeApplyPatch(payload) {
700
+ const changes = payload === null || payload === void 0 ? void 0 : payload.changes;
701
+ const files = [];
702
+ if (Array.isArray(changes)) {
703
+ for (const entry of changes) {
704
+ const file = entry && typeof entry === 'object'
705
+ ? entry.path ||
706
+ entry.file
707
+ : undefined;
708
+ if (file && typeof file === 'string') {
709
+ files.push(file);
710
+ }
711
+ }
712
+ }
713
+ else if (changes && typeof changes === 'object') {
714
+ for (const key of Object.keys(changes)) {
715
+ files.push(key);
716
+ }
717
+ }
718
+ const unique = Array.from(new Set(files));
719
+ const summary = unique.length === 0
720
+ ? 'Applied file changes'
721
+ : unique.length === 1
722
+ ? `Updated ${unique[0]}`
723
+ : `Updated ${unique.length} files (${unique
724
+ .slice(0, 3)
725
+ .join(', ')}${unique.length > 3 ? ', ...' : ''})`;
726
+ return {
727
+ content: summary,
728
+ metadata: {
729
+ files: unique,
730
+ },
731
+ };
732
+ }
733
+ extractTodoListItems(record) {
734
+ if (Array.isArray(record.items)) {
735
+ return record.items;
736
+ }
737
+ const nestedItem = record.item;
738
+ if (nestedItem &&
739
+ typeof nestedItem === 'object' &&
740
+ Array.isArray(nestedItem.items)) {
741
+ return nestedItem.items;
742
+ }
743
+ const delta = record.delta;
744
+ if (delta &&
745
+ typeof delta === 'object' &&
746
+ Array.isArray(delta.items)) {
747
+ return delta.items;
748
+ }
749
+ return [];
750
+ }
751
+ normalizeTodoListItems(input) {
752
+ if (!Array.isArray(input)) {
753
+ return [];
754
+ }
755
+ const result = [];
756
+ input.forEach((entry, index) => {
757
+ var _a;
758
+ if (!entry || typeof entry !== 'object') {
759
+ return;
760
+ }
761
+ const record = entry;
762
+ const text = (_a = this.pickFirstString(record.text)) !== null && _a !== void 0 ? _a : `Step ${index + 1}`;
763
+ const completed = record.completed === true || record.done === true;
764
+ result.push({
765
+ text,
766
+ completed,
767
+ index,
768
+ });
769
+ });
770
+ return result;
771
+ }
772
+ buildTodoListContent(items, phase) {
773
+ if (items.length === 0) {
774
+ switch (phase) {
775
+ case 'started':
776
+ return 'Started plan with no explicit steps.';
777
+ case 'completed':
778
+ return 'Plan completed.';
779
+ default:
780
+ return 'Plan updated.';
781
+ }
782
+ }
783
+ const header = phase === 'completed'
784
+ ? 'Plan completed:'
785
+ : phase === 'started'
786
+ ? 'Plan generated:'
787
+ : 'Plan updated:';
788
+ const stepLines = items.map((item, idx) => {
789
+ const bullet = item.completed ? '✅' : '⬜️';
790
+ const label = `Step ${idx + 1}`;
791
+ return `${bullet} ${label}: ${item.text}`;
792
+ });
793
+ return [header, ...stepLines].join('\n');
794
+ }
795
+ createTodoListMetadata(items, phase, extra) {
796
+ const totalSteps = items.length;
797
+ const completedSteps = items.filter((item) => item.completed).length;
798
+ return {
799
+ toolName: 'Plan',
800
+ tool_name: 'Plan',
801
+ planPhase: phase,
802
+ planStatus: phase === 'completed' ? 'completed' : 'in_progress',
803
+ totalSteps,
804
+ completedSteps,
805
+ items: items.map(({ text, completed, index }) => ({
806
+ text,
807
+ completed,
808
+ index,
809
+ })),
810
+ ...(extra !== null && extra !== void 0 ? extra : {}),
811
+ };
812
+ }
813
+ encodeHash(value) {
814
+ return Buffer.from(value, 'utf-8').toString('base64');
815
+ }
816
+ }
817
+ exports.CodexEngine = CodexEngine;
818
+ /**
819
+ * Maximum number of stderr lines to keep in memory to avoid unbounded growth.
820
+ */
821
+ CodexEngine.MAX_STDERR_LINES = 200;
822
+ //# sourceMappingURL=codex.js.map