happy-imou-cloud 1.1.7 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{setupOfflineReconnection-ndObLZk0.mjs → BaseReasoningProcessor-BKLRCKTU.mjs} +133 -90
- package/dist/{setupOfflineReconnection-obypStdD.cjs → BaseReasoningProcessor-BRCQXCZY.cjs} +134 -90
- package/dist/{types-BXyraW9R.mjs → api-BGXYX0yH.mjs} +198 -170
- package/dist/{types-BSTmyv9d.cjs → api-D7OK-mML.cjs} +219 -192
- package/dist/command-CnLtKtP-.mjs +51 -0
- package/dist/command-G85giEAF.cjs +54 -0
- package/dist/future-Dq4Ha1Dn.cjs +24 -0
- package/dist/future-xRdLl3vf.mjs +22 -0
- package/dist/{index-DVI4b0mv.cjs → index-B_wlQBy2.cjs} +5493 -7142
- package/dist/{index-CUmYqKWt.mjs → index-C7Y0R-MI.mjs} +5482 -7143
- package/dist/index.cjs +19 -21
- package/dist/index.mjs +19 -21
- package/dist/lib.cjs +3 -2
- package/dist/lib.d.cts +17 -0
- package/dist/lib.d.mts +17 -0
- package/dist/lib.mjs +2 -1
- package/dist/{persistence-BGsuPqaO.mjs → persistence-BA_unuca.mjs} +8 -4
- package/dist/{persistence-BRH9F6RS.cjs → persistence-DHgf1CTG.cjs} +10 -6
- package/dist/registerKillSessionHandler-C2-yHm1V.mjs +428 -0
- package/dist/registerKillSessionHandler-CLREXN11.cjs +433 -0
- package/dist/runClaude-CwAitpX-.cjs +3274 -0
- package/dist/runClaude-uNC5Eym4.mjs +3271 -0
- package/dist/runCodex-B-05E-YZ.mjs +1846 -0
- package/dist/runCodex-Cm0VTqw_.cjs +1848 -0
- package/dist/{runGemini-C3dDtGOV.cjs → runGemini-CLWjwDYS.cjs} +25 -1366
- package/dist/{runGemini-B-EK_BJQ.mjs → runGemini-_biXvQAH.mjs} +12 -1353
- package/dist/types-CiliQpqS.mjs +52 -0
- package/dist/types-DVk3crez.cjs +54 -0
- package/package.json +13 -12
- package/scripts/devtools/README.md +9 -0
- package/scripts/devtools/generate-mock-credentials.ts +94 -0
- package/scripts/release-smoke.mjs +62 -0
- package/dist/config-BQNrtwRY.cjs +0 -183
- package/dist/config-Dn99YH37.mjs +0 -173
- package/dist/runCodex-Cez8cuIh.cjs +0 -1143
- package/dist/runCodex-X0BfjcZH.mjs +0 -1140
|
@@ -1,1140 +0,0 @@
|
|
|
1
|
-
import { useStdout, useInput, Box, Text, render } from 'ink';
|
|
2
|
-
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
|
-
import { l as logger, b as connectionState, A as ApiClient } from './types-BXyraW9R.mjs';
|
|
4
|
-
import { B as BasePermissionHandler, a as BaseReasoningProcessor, c as createSessionMetadata, s as setupOfflineReconnection } from './setupOfflineReconnection-ndObLZk0.mjs';
|
|
5
|
-
import { randomUUID } from 'node:crypto';
|
|
6
|
-
import { readSettings } from './persistence-BGsuPqaO.mjs';
|
|
7
|
-
import { i as initialMachineMetadata, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, r as registerKillSessionHandler, a as MessageBuffer, s as stopCaffeinate } from './index-CUmYqKWt.mjs';
|
|
8
|
-
import os from 'node:os';
|
|
9
|
-
import { join } from 'node:path';
|
|
10
|
-
import fs from 'node:fs';
|
|
11
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
12
|
-
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
13
|
-
import { z } from 'zod';
|
|
14
|
-
import { ElicitRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
15
|
-
import { execSync } from 'child_process';
|
|
16
|
-
import 'axios';
|
|
17
|
-
import 'chalk';
|
|
18
|
-
import 'fs';
|
|
19
|
-
import 'node:events';
|
|
20
|
-
import 'socket.io-client';
|
|
21
|
-
import 'tweetnacl';
|
|
22
|
-
import 'util';
|
|
23
|
-
import 'fs/promises';
|
|
24
|
-
import 'crypto';
|
|
25
|
-
import 'path';
|
|
26
|
-
import 'expo-server-sdk';
|
|
27
|
-
import 'node:fs/promises';
|
|
28
|
-
import 'node:child_process';
|
|
29
|
-
import 'node:readline';
|
|
30
|
-
import 'url';
|
|
31
|
-
import 'node:url';
|
|
32
|
-
import 'node:util';
|
|
33
|
-
import 'ps-list';
|
|
34
|
-
import 'cross-spawn';
|
|
35
|
-
import 'os';
|
|
36
|
-
import 'tmp';
|
|
37
|
-
import 'qrcode-terminal';
|
|
38
|
-
import 'node:module';
|
|
39
|
-
import 'open';
|
|
40
|
-
import 'fastify';
|
|
41
|
-
import 'fastify-type-provider-zod';
|
|
42
|
-
import 'node:http';
|
|
43
|
-
import 'http';
|
|
44
|
-
|
|
45
|
-
class CodexPermissionHandler extends BasePermissionHandler {
|
|
46
|
-
constructor(session) {
|
|
47
|
-
super(session);
|
|
48
|
-
}
|
|
49
|
-
getLogPrefix() {
|
|
50
|
-
return "[Codex]";
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Handle a tool permission request
|
|
54
|
-
* @param toolCallId - The unique ID of the tool call
|
|
55
|
-
* @param toolName - The name of the tool being called
|
|
56
|
-
* @param input - The input parameters for the tool
|
|
57
|
-
* @returns Promise resolving to permission result
|
|
58
|
-
*/
|
|
59
|
-
async handleToolCall(toolCallId, toolName, input) {
|
|
60
|
-
return new Promise((resolve, reject) => {
|
|
61
|
-
this.pendingRequests.set(toolCallId, {
|
|
62
|
-
resolve,
|
|
63
|
-
reject,
|
|
64
|
-
toolName,
|
|
65
|
-
input
|
|
66
|
-
});
|
|
67
|
-
this.addPendingRequestToState(toolCallId, toolName, input);
|
|
68
|
-
logger.debug(`${this.getLogPrefix()} Permission request sent for tool: ${toolName} (${toolCallId})`);
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
class ReasoningProcessor extends BaseReasoningProcessor {
|
|
74
|
-
getToolName() {
|
|
75
|
-
return "CodexReasoning";
|
|
76
|
-
}
|
|
77
|
-
getLogPrefix() {
|
|
78
|
-
return "[ReasoningProcessor]";
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Process a reasoning delta and accumulate content.
|
|
82
|
-
*/
|
|
83
|
-
processDelta(delta) {
|
|
84
|
-
this.processInput(delta);
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Complete the reasoning section with final text.
|
|
88
|
-
*/
|
|
89
|
-
complete(fullText) {
|
|
90
|
-
this.completeReasoning(fullText);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
class DiffProcessor {
|
|
95
|
-
previousDiff = null;
|
|
96
|
-
onMessage = null;
|
|
97
|
-
constructor(onMessage) {
|
|
98
|
-
this.onMessage = onMessage || null;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Process a turn_diff message and check if the unified_diff has changed
|
|
102
|
-
*/
|
|
103
|
-
processDiff(unifiedDiff) {
|
|
104
|
-
if (this.previousDiff !== unifiedDiff) {
|
|
105
|
-
logger.debug("[DiffProcessor] Unified diff changed, sending CodexDiff tool call");
|
|
106
|
-
const callId = randomUUID();
|
|
107
|
-
const toolCall = {
|
|
108
|
-
type: "tool-call",
|
|
109
|
-
name: "CodexDiff",
|
|
110
|
-
callId,
|
|
111
|
-
input: {
|
|
112
|
-
unified_diff: unifiedDiff
|
|
113
|
-
},
|
|
114
|
-
id: randomUUID()
|
|
115
|
-
};
|
|
116
|
-
this.onMessage?.(toolCall);
|
|
117
|
-
const toolResult = {
|
|
118
|
-
type: "tool-call-result",
|
|
119
|
-
callId,
|
|
120
|
-
output: {
|
|
121
|
-
status: "completed"
|
|
122
|
-
},
|
|
123
|
-
id: randomUUID()
|
|
124
|
-
};
|
|
125
|
-
this.onMessage?.(toolResult);
|
|
126
|
-
}
|
|
127
|
-
this.previousDiff = unifiedDiff;
|
|
128
|
-
logger.debug("[DiffProcessor] Updated stored diff");
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Reset the processor state (called on task_complete or turn_aborted)
|
|
132
|
-
*/
|
|
133
|
-
reset() {
|
|
134
|
-
logger.debug("[DiffProcessor] Resetting diff state");
|
|
135
|
-
this.previousDiff = null;
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Set the message callback for sending messages directly
|
|
139
|
-
*/
|
|
140
|
-
setMessageCallback(callback) {
|
|
141
|
-
this.onMessage = callback;
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Get the current diff value
|
|
145
|
-
*/
|
|
146
|
-
getCurrentDiff() {
|
|
147
|
-
return this.previousDiff;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const CodexDisplay = ({ messageBuffer, logPath, onExit }) => {
|
|
152
|
-
const [messages, setMessages] = useState([]);
|
|
153
|
-
const [confirmationMode, setConfirmationMode] = useState(false);
|
|
154
|
-
const [actionInProgress, setActionInProgress] = useState(false);
|
|
155
|
-
const confirmationTimeoutRef = useRef(null);
|
|
156
|
-
const { stdout } = useStdout();
|
|
157
|
-
const terminalWidth = stdout.columns || 80;
|
|
158
|
-
const terminalHeight = stdout.rows || 24;
|
|
159
|
-
useEffect(() => {
|
|
160
|
-
setMessages(messageBuffer.getMessages());
|
|
161
|
-
const unsubscribe = messageBuffer.onUpdate((newMessages) => {
|
|
162
|
-
setMessages(newMessages);
|
|
163
|
-
});
|
|
164
|
-
return () => {
|
|
165
|
-
unsubscribe();
|
|
166
|
-
if (confirmationTimeoutRef.current) {
|
|
167
|
-
clearTimeout(confirmationTimeoutRef.current);
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
}, [messageBuffer]);
|
|
171
|
-
const resetConfirmation = useCallback(() => {
|
|
172
|
-
setConfirmationMode(false);
|
|
173
|
-
if (confirmationTimeoutRef.current) {
|
|
174
|
-
clearTimeout(confirmationTimeoutRef.current);
|
|
175
|
-
confirmationTimeoutRef.current = null;
|
|
176
|
-
}
|
|
177
|
-
}, []);
|
|
178
|
-
const setConfirmationWithTimeout = useCallback(() => {
|
|
179
|
-
setConfirmationMode(true);
|
|
180
|
-
if (confirmationTimeoutRef.current) {
|
|
181
|
-
clearTimeout(confirmationTimeoutRef.current);
|
|
182
|
-
}
|
|
183
|
-
confirmationTimeoutRef.current = setTimeout(() => {
|
|
184
|
-
resetConfirmation();
|
|
185
|
-
}, 15e3);
|
|
186
|
-
}, [resetConfirmation]);
|
|
187
|
-
useInput(useCallback(async (input, key) => {
|
|
188
|
-
if (actionInProgress) return;
|
|
189
|
-
if (key.ctrl && input === "c") {
|
|
190
|
-
if (confirmationMode) {
|
|
191
|
-
resetConfirmation();
|
|
192
|
-
setActionInProgress(true);
|
|
193
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
194
|
-
onExit?.();
|
|
195
|
-
} else {
|
|
196
|
-
setConfirmationWithTimeout();
|
|
197
|
-
}
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
if (confirmationMode) {
|
|
201
|
-
resetConfirmation();
|
|
202
|
-
}
|
|
203
|
-
}, [confirmationMode, actionInProgress, onExit, setConfirmationWithTimeout, resetConfirmation]));
|
|
204
|
-
const getMessageColor = (type) => {
|
|
205
|
-
switch (type) {
|
|
206
|
-
case "user":
|
|
207
|
-
return "magenta";
|
|
208
|
-
case "assistant":
|
|
209
|
-
return "cyan";
|
|
210
|
-
case "system":
|
|
211
|
-
return "blue";
|
|
212
|
-
case "tool":
|
|
213
|
-
return "yellow";
|
|
214
|
-
case "result":
|
|
215
|
-
return "green";
|
|
216
|
-
case "status":
|
|
217
|
-
return "gray";
|
|
218
|
-
default:
|
|
219
|
-
return "white";
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
const formatMessage = (msg) => {
|
|
223
|
-
const lines = msg.content.split("\n");
|
|
224
|
-
const maxLineLength = terminalWidth - 10;
|
|
225
|
-
return lines.map((line) => {
|
|
226
|
-
if (line.length <= maxLineLength) return line;
|
|
227
|
-
const chunks = [];
|
|
228
|
-
for (let i = 0; i < line.length; i += maxLineLength) {
|
|
229
|
-
chunks.push(line.slice(i, i + maxLineLength));
|
|
230
|
-
}
|
|
231
|
-
return chunks.join("\n");
|
|
232
|
-
}).join("\n");
|
|
233
|
-
};
|
|
234
|
-
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: terminalWidth, height: terminalHeight }, /* @__PURE__ */ React.createElement(
|
|
235
|
-
Box,
|
|
236
|
-
{
|
|
237
|
-
flexDirection: "column",
|
|
238
|
-
width: terminalWidth,
|
|
239
|
-
height: terminalHeight - 4,
|
|
240
|
-
borderStyle: "round",
|
|
241
|
-
borderColor: "gray",
|
|
242
|
-
paddingX: 1,
|
|
243
|
-
overflow: "hidden"
|
|
244
|
-
},
|
|
245
|
-
/* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "gray", bold: true }, "\u{1F916} Codex Agent Messages"), /* @__PURE__ */ React.createElement(Text, { color: "gray", dimColor: true }, "\u2500".repeat(Math.min(terminalWidth - 4, 60)))),
|
|
246
|
-
/* @__PURE__ */ React.createElement(Box, { flexDirection: "column", height: terminalHeight - 10, overflow: "hidden" }, messages.length === 0 ? /* @__PURE__ */ React.createElement(Text, { color: "gray", dimColor: true }, "Waiting for messages...") : (
|
|
247
|
-
// Show only the last messages that fit in the available space
|
|
248
|
-
messages.slice(-Math.max(1, terminalHeight - 10)).map((msg) => /* @__PURE__ */ React.createElement(Box, { key: msg.id, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: getMessageColor(msg.type), dimColor: true }, formatMessage(msg))))
|
|
249
|
-
))
|
|
250
|
-
), /* @__PURE__ */ React.createElement(
|
|
251
|
-
Box,
|
|
252
|
-
{
|
|
253
|
-
width: terminalWidth,
|
|
254
|
-
borderStyle: "round",
|
|
255
|
-
borderColor: actionInProgress ? "gray" : confirmationMode ? "red" : "green",
|
|
256
|
-
paddingX: 2,
|
|
257
|
-
justifyContent: "center",
|
|
258
|
-
alignItems: "center",
|
|
259
|
-
flexDirection: "column"
|
|
260
|
-
},
|
|
261
|
-
/* @__PURE__ */ React.createElement(Box, { flexDirection: "column", alignItems: "center" }, actionInProgress ? /* @__PURE__ */ React.createElement(Text, { color: "gray", bold: true }, "Exiting agent...") : confirmationMode ? /* @__PURE__ */ React.createElement(Text, { color: "red", bold: true }, "\u26A0\uFE0F Press Ctrl-C again to exit the agent") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Text, { color: "green", bold: true }, "\u{1F916} Codex Agent Running \u2022 Ctrl-C to exit")), process.env.DEBUG && logPath && /* @__PURE__ */ React.createElement(Text, { color: "gray", dimColor: true }, "Debug logs: ", logPath))
|
|
262
|
-
));
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
const DEFAULT_TIMEOUT = 14 * 24 * 60 * 60 * 1e3;
|
|
266
|
-
function getCodexMcpCommand() {
|
|
267
|
-
try {
|
|
268
|
-
const version = execSync("codex --version", { encoding: "utf8" }).trim();
|
|
269
|
-
const match = version.match(/codex-cli\s+(\d+\.\d+\.\d+(?:-alpha\.\d+)?)/);
|
|
270
|
-
if (!match) {
|
|
271
|
-
logger.debug("[CodexMCP] Could not parse codex version:", version);
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
|
-
const versionStr = match[1];
|
|
275
|
-
const [major, minor, patch] = versionStr.split(/[-.]/).map(Number);
|
|
276
|
-
if (major > 0 || minor > 43) return "mcp-server";
|
|
277
|
-
if (minor === 43 && patch === 0) {
|
|
278
|
-
if (versionStr.includes("-alpha.")) {
|
|
279
|
-
const alphaNum = parseInt(versionStr.split("-alpha.")[1]);
|
|
280
|
-
return alphaNum >= 5 ? "mcp-server" : "mcp";
|
|
281
|
-
}
|
|
282
|
-
return "mcp-server";
|
|
283
|
-
}
|
|
284
|
-
return "mcp";
|
|
285
|
-
} catch (error) {
|
|
286
|
-
logger.debug("[CodexMCP] Codex CLI not found or not executable:", error);
|
|
287
|
-
return null;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
class CodexMcpClient {
|
|
291
|
-
client;
|
|
292
|
-
transport = null;
|
|
293
|
-
connected = false;
|
|
294
|
-
sessionId = null;
|
|
295
|
-
conversationId = null;
|
|
296
|
-
handler = null;
|
|
297
|
-
permissionHandler = null;
|
|
298
|
-
constructor() {
|
|
299
|
-
this.client = new Client(
|
|
300
|
-
{ name: "happy-codex-client", version: "1.0.0" },
|
|
301
|
-
{ capabilities: { elicitation: {} } }
|
|
302
|
-
);
|
|
303
|
-
this.client.setNotificationHandler(z.object({
|
|
304
|
-
method: z.literal("codex/event"),
|
|
305
|
-
params: z.object({
|
|
306
|
-
msg: z.any()
|
|
307
|
-
})
|
|
308
|
-
}).passthrough(), (data) => {
|
|
309
|
-
const msg = data.params.msg;
|
|
310
|
-
this.updateIdentifiersFromEvent(msg);
|
|
311
|
-
this.handler?.(msg);
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
setHandler(handler) {
|
|
315
|
-
this.handler = handler;
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Set the permission handler for tool approval
|
|
319
|
-
*/
|
|
320
|
-
setPermissionHandler(handler) {
|
|
321
|
-
this.permissionHandler = handler;
|
|
322
|
-
}
|
|
323
|
-
async connect() {
|
|
324
|
-
if (this.connected) return;
|
|
325
|
-
const mcpCommand = getCodexMcpCommand();
|
|
326
|
-
if (mcpCommand === null) {
|
|
327
|
-
throw new Error(
|
|
328
|
-
"Codex CLI not found or not executable.\n\nTo install codex:\n npm install -g @openai/codex\n\nAlternatively, use Claude:\n happy claude"
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
logger.debug(`[CodexMCP] Connecting to Codex MCP server using command: codex ${mcpCommand}`);
|
|
332
|
-
this.transport = new StdioClientTransport({
|
|
333
|
-
command: "codex",
|
|
334
|
-
args: [mcpCommand],
|
|
335
|
-
env: Object.keys(process.env).reduce((acc, key) => {
|
|
336
|
-
const value = process.env[key];
|
|
337
|
-
if (typeof value === "string") acc[key] = value;
|
|
338
|
-
return acc;
|
|
339
|
-
}, {})
|
|
340
|
-
});
|
|
341
|
-
this.registerPermissionHandlers();
|
|
342
|
-
await this.client.connect(this.transport);
|
|
343
|
-
this.connected = true;
|
|
344
|
-
logger.debug("[CodexMCP] Connected to Codex");
|
|
345
|
-
}
|
|
346
|
-
registerPermissionHandlers() {
|
|
347
|
-
this.client.setRequestHandler(
|
|
348
|
-
ElicitRequestSchema,
|
|
349
|
-
async (request) => {
|
|
350
|
-
console.log("[CodexMCP] Received elicitation request:", request.params);
|
|
351
|
-
const params = request.params;
|
|
352
|
-
const toolName = "CodexBash";
|
|
353
|
-
if (!this.permissionHandler) {
|
|
354
|
-
logger.debug("[CodexMCP] No permission handler set, denying by default");
|
|
355
|
-
return {
|
|
356
|
-
decision: "denied"
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
try {
|
|
360
|
-
const result = await this.permissionHandler.handleToolCall(
|
|
361
|
-
params.codex_call_id,
|
|
362
|
-
toolName,
|
|
363
|
-
{
|
|
364
|
-
command: params.codex_command,
|
|
365
|
-
cwd: params.codex_cwd
|
|
366
|
-
}
|
|
367
|
-
);
|
|
368
|
-
logger.debug("[CodexMCP] Permission result:", result);
|
|
369
|
-
return {
|
|
370
|
-
decision: result.decision
|
|
371
|
-
};
|
|
372
|
-
} catch (error) {
|
|
373
|
-
logger.debug("[CodexMCP] Error handling permission request:", error);
|
|
374
|
-
return {
|
|
375
|
-
decision: "denied",
|
|
376
|
-
reason: error instanceof Error ? error.message : "Permission request failed"
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
);
|
|
381
|
-
logger.debug("[CodexMCP] Permission handlers registered");
|
|
382
|
-
}
|
|
383
|
-
async startSession(config, options) {
|
|
384
|
-
if (!this.connected) await this.connect();
|
|
385
|
-
logger.debug("[CodexMCP] Starting Codex session:", config);
|
|
386
|
-
const response = await this.client.callTool({
|
|
387
|
-
name: "codex",
|
|
388
|
-
arguments: config
|
|
389
|
-
}, void 0, {
|
|
390
|
-
signal: options?.signal,
|
|
391
|
-
timeout: DEFAULT_TIMEOUT
|
|
392
|
-
// maxTotalTimeout: 10000000000
|
|
393
|
-
});
|
|
394
|
-
logger.debug("[CodexMCP] startSession response:", response);
|
|
395
|
-
this.extractIdentifiers(response);
|
|
396
|
-
return response;
|
|
397
|
-
}
|
|
398
|
-
async continueSession(prompt, options) {
|
|
399
|
-
if (!this.connected) await this.connect();
|
|
400
|
-
if (!this.sessionId) {
|
|
401
|
-
throw new Error("No active session. Call startSession first.");
|
|
402
|
-
}
|
|
403
|
-
if (!this.conversationId) {
|
|
404
|
-
this.conversationId = this.sessionId;
|
|
405
|
-
logger.debug("[CodexMCP] conversationId missing, defaulting to sessionId:", this.conversationId);
|
|
406
|
-
}
|
|
407
|
-
const args = { sessionId: this.sessionId, conversationId: this.conversationId, prompt };
|
|
408
|
-
logger.debug("[CodexMCP] Continuing Codex session:", args);
|
|
409
|
-
const response = await this.client.callTool({
|
|
410
|
-
name: "codex-reply",
|
|
411
|
-
arguments: args
|
|
412
|
-
}, void 0, {
|
|
413
|
-
signal: options?.signal,
|
|
414
|
-
timeout: DEFAULT_TIMEOUT
|
|
415
|
-
});
|
|
416
|
-
logger.debug("[CodexMCP] continueSession response:", response);
|
|
417
|
-
this.extractIdentifiers(response);
|
|
418
|
-
return response;
|
|
419
|
-
}
|
|
420
|
-
updateIdentifiersFromEvent(event) {
|
|
421
|
-
if (!event || typeof event !== "object") {
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
const candidates = [event];
|
|
425
|
-
if (event.data && typeof event.data === "object") {
|
|
426
|
-
candidates.push(event.data);
|
|
427
|
-
}
|
|
428
|
-
for (const candidate of candidates) {
|
|
429
|
-
const sessionId = candidate.session_id ?? candidate.sessionId;
|
|
430
|
-
if (sessionId) {
|
|
431
|
-
this.sessionId = sessionId;
|
|
432
|
-
logger.debug("[CodexMCP] Session ID extracted from event:", this.sessionId);
|
|
433
|
-
}
|
|
434
|
-
const conversationId = candidate.conversation_id ?? candidate.conversationId;
|
|
435
|
-
if (conversationId) {
|
|
436
|
-
this.conversationId = conversationId;
|
|
437
|
-
logger.debug("[CodexMCP] Conversation ID extracted from event:", this.conversationId);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
extractIdentifiers(response) {
|
|
442
|
-
const meta = response?.meta || {};
|
|
443
|
-
if (meta.sessionId) {
|
|
444
|
-
this.sessionId = meta.sessionId;
|
|
445
|
-
logger.debug("[CodexMCP] Session ID extracted:", this.sessionId);
|
|
446
|
-
} else if (response?.sessionId) {
|
|
447
|
-
this.sessionId = response.sessionId;
|
|
448
|
-
logger.debug("[CodexMCP] Session ID extracted:", this.sessionId);
|
|
449
|
-
}
|
|
450
|
-
if (meta.conversationId) {
|
|
451
|
-
this.conversationId = meta.conversationId;
|
|
452
|
-
logger.debug("[CodexMCP] Conversation ID extracted:", this.conversationId);
|
|
453
|
-
} else if (response?.conversationId) {
|
|
454
|
-
this.conversationId = response.conversationId;
|
|
455
|
-
logger.debug("[CodexMCP] Conversation ID extracted:", this.conversationId);
|
|
456
|
-
}
|
|
457
|
-
const content = response?.content;
|
|
458
|
-
if (Array.isArray(content)) {
|
|
459
|
-
for (const item of content) {
|
|
460
|
-
if (!this.sessionId && item?.sessionId) {
|
|
461
|
-
this.sessionId = item.sessionId;
|
|
462
|
-
logger.debug("[CodexMCP] Session ID extracted from content:", this.sessionId);
|
|
463
|
-
}
|
|
464
|
-
if (!this.conversationId && item && typeof item === "object" && "conversationId" in item && item.conversationId) {
|
|
465
|
-
this.conversationId = item.conversationId;
|
|
466
|
-
logger.debug("[CodexMCP] Conversation ID extracted from content:", this.conversationId);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
getSessionId() {
|
|
472
|
-
return this.sessionId;
|
|
473
|
-
}
|
|
474
|
-
hasActiveSession() {
|
|
475
|
-
return this.sessionId !== null;
|
|
476
|
-
}
|
|
477
|
-
clearSession() {
|
|
478
|
-
const previousSessionId = this.sessionId;
|
|
479
|
-
this.sessionId = null;
|
|
480
|
-
this.conversationId = null;
|
|
481
|
-
logger.debug("[CodexMCP] Session cleared, previous sessionId:", previousSessionId);
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* Store the current session ID without clearing it, useful for abort handling
|
|
485
|
-
*/
|
|
486
|
-
storeSessionForResume() {
|
|
487
|
-
logger.debug("[CodexMCP] Storing session for potential resume:", this.sessionId);
|
|
488
|
-
return this.sessionId;
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* Force close the Codex MCP transport and clear all session identifiers.
|
|
492
|
-
* Use this for permanent shutdown (e.g. kill/exit). Prefer `disconnect()` for
|
|
493
|
-
* transient connection resets where you may want to keep the session id.
|
|
494
|
-
*/
|
|
495
|
-
async forceCloseSession() {
|
|
496
|
-
logger.debug("[CodexMCP] Force closing session");
|
|
497
|
-
try {
|
|
498
|
-
await this.disconnect();
|
|
499
|
-
} finally {
|
|
500
|
-
this.clearSession();
|
|
501
|
-
}
|
|
502
|
-
logger.debug("[CodexMCP] Session force-closed");
|
|
503
|
-
}
|
|
504
|
-
async disconnect() {
|
|
505
|
-
if (!this.connected) return;
|
|
506
|
-
const pid = this.transport?.pid ?? null;
|
|
507
|
-
logger.debug(`[CodexMCP] Disconnecting; child pid=${pid ?? "none"}`);
|
|
508
|
-
try {
|
|
509
|
-
logger.debug("[CodexMCP] client.close begin");
|
|
510
|
-
await this.client.close();
|
|
511
|
-
logger.debug("[CodexMCP] client.close done");
|
|
512
|
-
} catch (e) {
|
|
513
|
-
logger.debug("[CodexMCP] Error closing client, attempting transport close directly", e);
|
|
514
|
-
try {
|
|
515
|
-
logger.debug("[CodexMCP] transport.close begin");
|
|
516
|
-
await this.transport?.close?.();
|
|
517
|
-
logger.debug("[CodexMCP] transport.close done");
|
|
518
|
-
} catch {
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
if (pid) {
|
|
522
|
-
try {
|
|
523
|
-
process.kill(pid, 0);
|
|
524
|
-
logger.debug("[CodexMCP] Child still alive, sending SIGKILL");
|
|
525
|
-
try {
|
|
526
|
-
process.kill(pid, "SIGKILL");
|
|
527
|
-
} catch {
|
|
528
|
-
}
|
|
529
|
-
} catch {
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
this.transport = null;
|
|
533
|
-
this.connected = false;
|
|
534
|
-
logger.debug(`[CodexMCP] Disconnected; session ${this.sessionId ?? "none"} preserved`);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
function emitReadyIfIdle({ pending, queueSize, shouldExit, sendReady, notify }) {
|
|
539
|
-
if (shouldExit) {
|
|
540
|
-
return false;
|
|
541
|
-
}
|
|
542
|
-
if (pending) {
|
|
543
|
-
return false;
|
|
544
|
-
}
|
|
545
|
-
if (queueSize() > 0) {
|
|
546
|
-
return false;
|
|
547
|
-
}
|
|
548
|
-
sendReady();
|
|
549
|
-
notify?.();
|
|
550
|
-
return true;
|
|
551
|
-
}
|
|
552
|
-
async function runCodex(opts) {
|
|
553
|
-
const sessionTag = randomUUID();
|
|
554
|
-
connectionState.setBackend("Codex");
|
|
555
|
-
const api = await ApiClient.create(opts.credentials);
|
|
556
|
-
logger.debug(`[codex] Starting with options: startedBy=${opts.startedBy || "terminal"}`);
|
|
557
|
-
const settings = await readSettings();
|
|
558
|
-
let machineId = settings?.machineId;
|
|
559
|
-
if (!machineId) {
|
|
560
|
-
console.error(`[START] No machine ID found in settings, which is unexpected since authAndSetupMachineIfNeeded should have created it. Please report this issue on https://github.com/slopus/happy-cli/issues`);
|
|
561
|
-
process.exit(1);
|
|
562
|
-
}
|
|
563
|
-
logger.debug(`Using machineId: ${machineId}`);
|
|
564
|
-
await api.getOrCreateMachine({
|
|
565
|
-
machineId,
|
|
566
|
-
metadata: initialMachineMetadata
|
|
567
|
-
});
|
|
568
|
-
const { state, metadata } = createSessionMetadata({
|
|
569
|
-
flavor: "codex",
|
|
570
|
-
machineId,
|
|
571
|
-
startedBy: opts.startedBy
|
|
572
|
-
});
|
|
573
|
-
const response = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
|
|
574
|
-
let session;
|
|
575
|
-
let permissionHandler;
|
|
576
|
-
const { session: initialSession, reconnectionHandle } = setupOfflineReconnection({
|
|
577
|
-
api,
|
|
578
|
-
sessionTag,
|
|
579
|
-
metadata,
|
|
580
|
-
state,
|
|
581
|
-
response,
|
|
582
|
-
onSessionSwap: (newSession) => {
|
|
583
|
-
session = newSession;
|
|
584
|
-
if (permissionHandler) {
|
|
585
|
-
permissionHandler.updateSession(newSession);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
});
|
|
589
|
-
session = initialSession;
|
|
590
|
-
if (response) {
|
|
591
|
-
try {
|
|
592
|
-
logger.debug(`[START] Reporting session ${response.id} to daemon`);
|
|
593
|
-
const result = await notifyDaemonSessionStarted(response.id, metadata);
|
|
594
|
-
if (result.error) {
|
|
595
|
-
logger.debug(`[START] Failed to report to daemon (may not be running):`, result.error);
|
|
596
|
-
} else {
|
|
597
|
-
logger.debug(`[START] Reported session ${response.id} to daemon`);
|
|
598
|
-
}
|
|
599
|
-
} catch (error) {
|
|
600
|
-
logger.debug("[START] Failed to report to daemon (may not be running):", error);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
const messageQueue = new MessageQueue2((mode) => hashObject({
|
|
604
|
-
permissionMode: mode.permissionMode,
|
|
605
|
-
model: mode.model
|
|
606
|
-
}));
|
|
607
|
-
let currentPermissionMode = void 0;
|
|
608
|
-
let currentModel = void 0;
|
|
609
|
-
session.onUserMessage((message) => {
|
|
610
|
-
let messagePermissionMode = currentPermissionMode;
|
|
611
|
-
if (message.meta?.permissionMode) {
|
|
612
|
-
messagePermissionMode = message.meta.permissionMode;
|
|
613
|
-
currentPermissionMode = messagePermissionMode;
|
|
614
|
-
logger.debug(`[Codex] Permission mode updated from user message to: ${currentPermissionMode}`);
|
|
615
|
-
} else {
|
|
616
|
-
logger.debug(`[Codex] User message received with no permission mode override, using current: ${currentPermissionMode ?? "default (effective)"}`);
|
|
617
|
-
}
|
|
618
|
-
let messageModel = currentModel;
|
|
619
|
-
if (message.meta?.hasOwnProperty("model")) {
|
|
620
|
-
messageModel = message.meta.model || void 0;
|
|
621
|
-
currentModel = messageModel;
|
|
622
|
-
logger.debug(`[Codex] Model updated from user message: ${messageModel || "reset to default"}`);
|
|
623
|
-
} else {
|
|
624
|
-
logger.debug(`[Codex] User message received with no model override, using current: ${currentModel || "default"}`);
|
|
625
|
-
}
|
|
626
|
-
const enhancedMode = {
|
|
627
|
-
permissionMode: messagePermissionMode || "default",
|
|
628
|
-
model: messageModel
|
|
629
|
-
};
|
|
630
|
-
messageQueue.push(message.content.text, enhancedMode);
|
|
631
|
-
});
|
|
632
|
-
let thinking = false;
|
|
633
|
-
session.keepAlive(thinking, "remote");
|
|
634
|
-
const keepAliveInterval = setInterval(() => {
|
|
635
|
-
session.keepAlive(thinking, "remote");
|
|
636
|
-
}, 2e3);
|
|
637
|
-
const sendReady = () => {
|
|
638
|
-
session.sendSessionEvent({ type: "ready" });
|
|
639
|
-
try {
|
|
640
|
-
api.push().sendToAllDevices(
|
|
641
|
-
"It's ready!",
|
|
642
|
-
"Codex is waiting for your command",
|
|
643
|
-
{ sessionId: session.sessionId }
|
|
644
|
-
);
|
|
645
|
-
} catch (pushError) {
|
|
646
|
-
logger.debug("[Codex] Failed to send ready push", pushError);
|
|
647
|
-
}
|
|
648
|
-
};
|
|
649
|
-
function logActiveHandles(tag) {
|
|
650
|
-
if (!process.env.DEBUG) return;
|
|
651
|
-
const anyProc = process;
|
|
652
|
-
const handles = typeof anyProc._getActiveHandles === "function" ? anyProc._getActiveHandles() : [];
|
|
653
|
-
const requests = typeof anyProc._getActiveRequests === "function" ? anyProc._getActiveRequests() : [];
|
|
654
|
-
logger.debug(`[codex][handles] ${tag}: handles=${handles.length} requests=${requests.length}`);
|
|
655
|
-
try {
|
|
656
|
-
const kinds = handles.map((h) => h && h.constructor ? h.constructor.name : typeof h);
|
|
657
|
-
logger.debug(`[codex][handles] kinds=${JSON.stringify(kinds)}`);
|
|
658
|
-
} catch {
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
let abortController = new AbortController();
|
|
662
|
-
let shouldExit = false;
|
|
663
|
-
let storedSessionIdForResume = null;
|
|
664
|
-
async function handleAbort() {
|
|
665
|
-
logger.debug("[Codex] Abort requested - stopping current task");
|
|
666
|
-
try {
|
|
667
|
-
if (client.hasActiveSession()) {
|
|
668
|
-
storedSessionIdForResume = client.storeSessionForResume();
|
|
669
|
-
logger.debug("[Codex] Stored session for resume:", storedSessionIdForResume);
|
|
670
|
-
}
|
|
671
|
-
abortController.abort();
|
|
672
|
-
reasoningProcessor.abort();
|
|
673
|
-
logger.debug("[Codex] Abort completed - session remains active");
|
|
674
|
-
} catch (error) {
|
|
675
|
-
logger.debug("[Codex] Error during abort:", error);
|
|
676
|
-
} finally {
|
|
677
|
-
abortController = new AbortController();
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
const handleKillSession = async () => {
|
|
681
|
-
logger.debug("[Codex] Kill session requested - terminating process");
|
|
682
|
-
await handleAbort();
|
|
683
|
-
logger.debug("[Codex] Abort completed, proceeding with termination");
|
|
684
|
-
try {
|
|
685
|
-
if (session) {
|
|
686
|
-
session.updateMetadata((currentMetadata) => ({
|
|
687
|
-
...currentMetadata,
|
|
688
|
-
lifecycleState: "archived",
|
|
689
|
-
lifecycleStateSince: Date.now(),
|
|
690
|
-
archivedBy: "cli",
|
|
691
|
-
archiveReason: "User terminated"
|
|
692
|
-
}));
|
|
693
|
-
session.sendSessionDeath();
|
|
694
|
-
await session.flush();
|
|
695
|
-
await session.close();
|
|
696
|
-
}
|
|
697
|
-
try {
|
|
698
|
-
await client.forceCloseSession();
|
|
699
|
-
} catch (e) {
|
|
700
|
-
logger.debug("[Codex] Error while force closing Codex session during termination", e);
|
|
701
|
-
}
|
|
702
|
-
stopCaffeinate();
|
|
703
|
-
logger.debug("[Codex] Session termination complete, exiting");
|
|
704
|
-
process.exit(0);
|
|
705
|
-
} catch (error) {
|
|
706
|
-
logger.debug("[Codex] Error during session termination:", error);
|
|
707
|
-
process.exit(1);
|
|
708
|
-
}
|
|
709
|
-
};
|
|
710
|
-
session.rpcHandlerManager.registerHandler("abort", handleAbort);
|
|
711
|
-
registerKillSessionHandler(session.rpcHandlerManager, handleKillSession);
|
|
712
|
-
const messageBuffer = new MessageBuffer();
|
|
713
|
-
const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
|
|
714
|
-
let inkInstance = null;
|
|
715
|
-
if (hasTTY) {
|
|
716
|
-
console.clear();
|
|
717
|
-
inkInstance = render(React.createElement(CodexDisplay, {
|
|
718
|
-
messageBuffer,
|
|
719
|
-
logPath: process.env.DEBUG ? logger.getLogPath() : void 0,
|
|
720
|
-
onExit: async () => {
|
|
721
|
-
logger.debug("[codex]: Exiting agent via Ctrl-C");
|
|
722
|
-
shouldExit = true;
|
|
723
|
-
await handleAbort();
|
|
724
|
-
}
|
|
725
|
-
}), {
|
|
726
|
-
exitOnCtrlC: false,
|
|
727
|
-
patchConsole: false
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
if (hasTTY) {
|
|
731
|
-
process.stdin.resume();
|
|
732
|
-
if (process.stdin.isTTY) {
|
|
733
|
-
process.stdin.setRawMode(true);
|
|
734
|
-
}
|
|
735
|
-
process.stdin.setEncoding("utf8");
|
|
736
|
-
}
|
|
737
|
-
const client = new CodexMcpClient();
|
|
738
|
-
function findCodexResumeFile(sessionId) {
|
|
739
|
-
if (!sessionId) return null;
|
|
740
|
-
try {
|
|
741
|
-
let collectFilesRecursive2 = function(dir, acc = []) {
|
|
742
|
-
let entries;
|
|
743
|
-
try {
|
|
744
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
745
|
-
} catch {
|
|
746
|
-
return acc;
|
|
747
|
-
}
|
|
748
|
-
for (const entry of entries) {
|
|
749
|
-
const full = join(dir, entry.name);
|
|
750
|
-
if (entry.isDirectory()) {
|
|
751
|
-
collectFilesRecursive2(full, acc);
|
|
752
|
-
} else if (entry.isFile()) {
|
|
753
|
-
acc.push(full);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
return acc;
|
|
757
|
-
};
|
|
758
|
-
var collectFilesRecursive = collectFilesRecursive2;
|
|
759
|
-
const codexHomeDir = process.env.CODEX_HOME || join(os.homedir(), ".codex");
|
|
760
|
-
const rootDir = join(codexHomeDir, "sessions");
|
|
761
|
-
const candidates = collectFilesRecursive2(rootDir).filter((full) => full.endsWith(`-${sessionId}.jsonl`)).filter((full) => {
|
|
762
|
-
try {
|
|
763
|
-
return fs.statSync(full).isFile();
|
|
764
|
-
} catch {
|
|
765
|
-
return false;
|
|
766
|
-
}
|
|
767
|
-
}).sort((a, b) => {
|
|
768
|
-
const sa = fs.statSync(a).mtimeMs;
|
|
769
|
-
const sb = fs.statSync(b).mtimeMs;
|
|
770
|
-
return sb - sa;
|
|
771
|
-
});
|
|
772
|
-
return candidates[0] || null;
|
|
773
|
-
} catch {
|
|
774
|
-
return null;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
permissionHandler = new CodexPermissionHandler(session);
|
|
778
|
-
const reasoningProcessor = new ReasoningProcessor((message) => {
|
|
779
|
-
session.sendCodexMessage(message);
|
|
780
|
-
});
|
|
781
|
-
const diffProcessor = new DiffProcessor((message) => {
|
|
782
|
-
session.sendCodexMessage(message);
|
|
783
|
-
});
|
|
784
|
-
client.setPermissionHandler(permissionHandler);
|
|
785
|
-
client.setHandler((msg) => {
|
|
786
|
-
logger.debug(`[Codex] Message: ${JSON.stringify(msg)}`);
|
|
787
|
-
if (msg.type === "agent_message") {
|
|
788
|
-
messageBuffer.addMessage(msg.message, "assistant");
|
|
789
|
-
} else if (msg.type === "agent_reasoning_delta") ; else if (msg.type === "agent_reasoning") {
|
|
790
|
-
messageBuffer.addMessage(`[Thinking] ${msg.text.substring(0, 100)}...`, "system");
|
|
791
|
-
} else if (msg.type === "exec_command_begin") {
|
|
792
|
-
messageBuffer.addMessage(`Executing: ${msg.command}`, "tool");
|
|
793
|
-
} else if (msg.type === "exec_command_end") {
|
|
794
|
-
const output = msg.output || msg.error || "Command completed";
|
|
795
|
-
const truncatedOutput = output.substring(0, 200);
|
|
796
|
-
messageBuffer.addMessage(
|
|
797
|
-
`Result: ${truncatedOutput}${output.length > 200 ? "..." : ""}`,
|
|
798
|
-
"result"
|
|
799
|
-
);
|
|
800
|
-
} else if (msg.type === "task_started") {
|
|
801
|
-
messageBuffer.addMessage("Starting task...", "status");
|
|
802
|
-
} else if (msg.type === "task_complete") {
|
|
803
|
-
messageBuffer.addMessage("Task completed", "status");
|
|
804
|
-
sendReady();
|
|
805
|
-
} else if (msg.type === "turn_aborted") {
|
|
806
|
-
messageBuffer.addMessage("Turn aborted", "status");
|
|
807
|
-
sendReady();
|
|
808
|
-
}
|
|
809
|
-
if (msg.type === "task_started") {
|
|
810
|
-
if (!thinking) {
|
|
811
|
-
logger.debug("thinking started");
|
|
812
|
-
thinking = true;
|
|
813
|
-
session.keepAlive(thinking, "remote");
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
if (msg.type === "task_complete" || msg.type === "turn_aborted") {
|
|
817
|
-
if (thinking) {
|
|
818
|
-
logger.debug("thinking completed");
|
|
819
|
-
thinking = false;
|
|
820
|
-
session.keepAlive(thinking, "remote");
|
|
821
|
-
}
|
|
822
|
-
diffProcessor.reset();
|
|
823
|
-
}
|
|
824
|
-
if (msg.type === "agent_reasoning_section_break") {
|
|
825
|
-
reasoningProcessor.handleSectionBreak();
|
|
826
|
-
}
|
|
827
|
-
if (msg.type === "agent_reasoning_delta") {
|
|
828
|
-
reasoningProcessor.processDelta(msg.delta);
|
|
829
|
-
}
|
|
830
|
-
if (msg.type === "agent_reasoning") {
|
|
831
|
-
reasoningProcessor.complete(msg.text);
|
|
832
|
-
}
|
|
833
|
-
if (msg.type === "agent_message") {
|
|
834
|
-
session.sendCodexMessage({
|
|
835
|
-
type: "message",
|
|
836
|
-
message: msg.message,
|
|
837
|
-
id: randomUUID()
|
|
838
|
-
});
|
|
839
|
-
}
|
|
840
|
-
if (msg.type === "exec_command_begin" || msg.type === "exec_approval_request") {
|
|
841
|
-
let { call_id, type, ...inputs } = msg;
|
|
842
|
-
session.sendCodexMessage({
|
|
843
|
-
type: "tool-call",
|
|
844
|
-
name: "CodexBash",
|
|
845
|
-
callId: call_id,
|
|
846
|
-
input: inputs,
|
|
847
|
-
id: randomUUID()
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
if (msg.type === "exec_command_end") {
|
|
851
|
-
let { call_id, type, ...output } = msg;
|
|
852
|
-
session.sendCodexMessage({
|
|
853
|
-
type: "tool-call-result",
|
|
854
|
-
callId: call_id,
|
|
855
|
-
output,
|
|
856
|
-
id: randomUUID()
|
|
857
|
-
});
|
|
858
|
-
}
|
|
859
|
-
if (msg.type === "token_count") {
|
|
860
|
-
session.sendCodexMessage({
|
|
861
|
-
...msg,
|
|
862
|
-
id: randomUUID()
|
|
863
|
-
});
|
|
864
|
-
}
|
|
865
|
-
if (msg.type === "patch_apply_begin") {
|
|
866
|
-
let { call_id, auto_approved, changes } = msg;
|
|
867
|
-
const changeCount = Object.keys(changes).length;
|
|
868
|
-
const filesMsg = changeCount === 1 ? "1 file" : `${changeCount} files`;
|
|
869
|
-
messageBuffer.addMessage(`Modifying ${filesMsg}...`, "tool");
|
|
870
|
-
session.sendCodexMessage({
|
|
871
|
-
type: "tool-call",
|
|
872
|
-
name: "CodexPatch",
|
|
873
|
-
callId: call_id,
|
|
874
|
-
input: {
|
|
875
|
-
auto_approved,
|
|
876
|
-
changes
|
|
877
|
-
},
|
|
878
|
-
id: randomUUID()
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
|
-
if (msg.type === "patch_apply_end") {
|
|
882
|
-
let { call_id, stdout, stderr, success } = msg;
|
|
883
|
-
if (success) {
|
|
884
|
-
const message = stdout || "Files modified successfully";
|
|
885
|
-
messageBuffer.addMessage(message.substring(0, 200), "result");
|
|
886
|
-
} else {
|
|
887
|
-
const errorMsg = stderr || "Failed to modify files";
|
|
888
|
-
messageBuffer.addMessage(`Error: ${errorMsg.substring(0, 200)}`, "result");
|
|
889
|
-
}
|
|
890
|
-
session.sendCodexMessage({
|
|
891
|
-
type: "tool-call-result",
|
|
892
|
-
callId: call_id,
|
|
893
|
-
output: {
|
|
894
|
-
stdout,
|
|
895
|
-
stderr,
|
|
896
|
-
success
|
|
897
|
-
},
|
|
898
|
-
id: randomUUID()
|
|
899
|
-
});
|
|
900
|
-
}
|
|
901
|
-
if (msg.type === "turn_diff") {
|
|
902
|
-
if (msg.unified_diff) {
|
|
903
|
-
diffProcessor.processDiff(msg.unified_diff);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
});
|
|
907
|
-
let first = true;
|
|
908
|
-
try {
|
|
909
|
-
logger.debug("[codex]: client.connect begin");
|
|
910
|
-
await client.connect();
|
|
911
|
-
logger.debug("[codex]: client.connect done");
|
|
912
|
-
let wasCreated = false;
|
|
913
|
-
let currentModeHash = null;
|
|
914
|
-
let pending = null;
|
|
915
|
-
let nextExperimentalResume = null;
|
|
916
|
-
while (!shouldExit) {
|
|
917
|
-
logActiveHandles("loop-top");
|
|
918
|
-
let message = pending;
|
|
919
|
-
pending = null;
|
|
920
|
-
if (!message) {
|
|
921
|
-
const waitSignal = abortController.signal;
|
|
922
|
-
const batch = await messageQueue.waitForMessagesAndGetAsString(waitSignal);
|
|
923
|
-
if (!batch) {
|
|
924
|
-
if (waitSignal.aborted && !shouldExit) {
|
|
925
|
-
logger.debug("[codex]: Wait aborted while idle; ignoring and continuing");
|
|
926
|
-
continue;
|
|
927
|
-
}
|
|
928
|
-
logger.debug(`[codex]: batch=${!!batch}, shouldExit=${shouldExit}`);
|
|
929
|
-
break;
|
|
930
|
-
}
|
|
931
|
-
message = batch;
|
|
932
|
-
}
|
|
933
|
-
if (!message) {
|
|
934
|
-
break;
|
|
935
|
-
}
|
|
936
|
-
if (wasCreated && currentModeHash && message.hash !== currentModeHash) {
|
|
937
|
-
logger.debug("[Codex] Mode changed \u2013 restarting Codex session");
|
|
938
|
-
messageBuffer.addMessage("\u2550".repeat(40), "status");
|
|
939
|
-
messageBuffer.addMessage("Starting new Codex session (mode changed)...", "status");
|
|
940
|
-
try {
|
|
941
|
-
const prevSessionId = client.getSessionId();
|
|
942
|
-
nextExperimentalResume = findCodexResumeFile(prevSessionId);
|
|
943
|
-
if (nextExperimentalResume) {
|
|
944
|
-
logger.debug(`[Codex] Found resume file for session ${prevSessionId}: ${nextExperimentalResume}`);
|
|
945
|
-
messageBuffer.addMessage("Resuming previous context\u2026", "status");
|
|
946
|
-
} else {
|
|
947
|
-
logger.debug("[Codex] No resume file found for previous session");
|
|
948
|
-
}
|
|
949
|
-
} catch (e) {
|
|
950
|
-
logger.debug("[Codex] Error while searching resume file", e);
|
|
951
|
-
}
|
|
952
|
-
client.clearSession();
|
|
953
|
-
wasCreated = false;
|
|
954
|
-
currentModeHash = null;
|
|
955
|
-
pending = message;
|
|
956
|
-
permissionHandler.reset();
|
|
957
|
-
reasoningProcessor.abort();
|
|
958
|
-
diffProcessor.reset();
|
|
959
|
-
thinking = false;
|
|
960
|
-
session.keepAlive(thinking, "remote");
|
|
961
|
-
continue;
|
|
962
|
-
}
|
|
963
|
-
messageBuffer.addMessage(message.message, "user");
|
|
964
|
-
currentModeHash = message.hash;
|
|
965
|
-
try {
|
|
966
|
-
const approvalPolicy = (() => {
|
|
967
|
-
switch (message.mode.permissionMode) {
|
|
968
|
-
// Codex native modes
|
|
969
|
-
case "default":
|
|
970
|
-
return "untrusted";
|
|
971
|
-
// Ask for non-trusted commands
|
|
972
|
-
case "read-only":
|
|
973
|
-
return "never";
|
|
974
|
-
// Never ask, read-only enforced by sandbox
|
|
975
|
-
case "safe-yolo":
|
|
976
|
-
return "on-failure";
|
|
977
|
-
// Auto-run, ask only on failure
|
|
978
|
-
case "yolo":
|
|
979
|
-
return "on-failure";
|
|
980
|
-
// Auto-run, ask only on failure
|
|
981
|
-
// Defensive fallback for Claude-specific modes (backward compatibility)
|
|
982
|
-
case "bypassPermissions":
|
|
983
|
-
return "on-failure";
|
|
984
|
-
// Full access: map to yolo behavior
|
|
985
|
-
case "acceptEdits":
|
|
986
|
-
return "on-request";
|
|
987
|
-
// Let model decide (closest to auto-approve edits)
|
|
988
|
-
case "plan":
|
|
989
|
-
return "untrusted";
|
|
990
|
-
// Conservative: ask for non-trusted
|
|
991
|
-
default:
|
|
992
|
-
return "untrusted";
|
|
993
|
-
}
|
|
994
|
-
})();
|
|
995
|
-
const sandbox = (() => {
|
|
996
|
-
switch (message.mode.permissionMode) {
|
|
997
|
-
// Codex native modes
|
|
998
|
-
case "default":
|
|
999
|
-
return "workspace-write";
|
|
1000
|
-
// Can write in workspace
|
|
1001
|
-
case "read-only":
|
|
1002
|
-
return "read-only";
|
|
1003
|
-
// Read-only filesystem
|
|
1004
|
-
case "safe-yolo":
|
|
1005
|
-
return "workspace-write";
|
|
1006
|
-
// Can write in workspace
|
|
1007
|
-
case "yolo":
|
|
1008
|
-
return "danger-full-access";
|
|
1009
|
-
// Full system access
|
|
1010
|
-
// Defensive fallback for Claude-specific modes
|
|
1011
|
-
case "bypassPermissions":
|
|
1012
|
-
return "danger-full-access";
|
|
1013
|
-
// Full access: map to yolo
|
|
1014
|
-
case "acceptEdits":
|
|
1015
|
-
return "workspace-write";
|
|
1016
|
-
// Can edit files in workspace
|
|
1017
|
-
case "plan":
|
|
1018
|
-
return "workspace-write";
|
|
1019
|
-
// Can write for planning
|
|
1020
|
-
default:
|
|
1021
|
-
return "workspace-write";
|
|
1022
|
-
}
|
|
1023
|
-
})();
|
|
1024
|
-
if (!wasCreated) {
|
|
1025
|
-
const startConfig = {
|
|
1026
|
-
prompt: message.message,
|
|
1027
|
-
sandbox,
|
|
1028
|
-
"approval-policy": approvalPolicy
|
|
1029
|
-
};
|
|
1030
|
-
if (message.mode.model) {
|
|
1031
|
-
startConfig.model = message.mode.model;
|
|
1032
|
-
}
|
|
1033
|
-
let resumeFile = null;
|
|
1034
|
-
if (nextExperimentalResume) {
|
|
1035
|
-
resumeFile = nextExperimentalResume;
|
|
1036
|
-
nextExperimentalResume = null;
|
|
1037
|
-
logger.debug("[Codex] Using resume file from mode change:", resumeFile);
|
|
1038
|
-
} else if (storedSessionIdForResume) {
|
|
1039
|
-
const abortResumeFile = findCodexResumeFile(storedSessionIdForResume);
|
|
1040
|
-
if (abortResumeFile) {
|
|
1041
|
-
resumeFile = abortResumeFile;
|
|
1042
|
-
logger.debug("[Codex] Using resume file from aborted session:", resumeFile);
|
|
1043
|
-
messageBuffer.addMessage("Resuming from aborted session...", "status");
|
|
1044
|
-
}
|
|
1045
|
-
storedSessionIdForResume = null;
|
|
1046
|
-
}
|
|
1047
|
-
if (resumeFile) {
|
|
1048
|
-
startConfig.config.experimental_resume = resumeFile;
|
|
1049
|
-
}
|
|
1050
|
-
await client.startSession(
|
|
1051
|
-
startConfig,
|
|
1052
|
-
{ signal: abortController.signal }
|
|
1053
|
-
);
|
|
1054
|
-
wasCreated = true;
|
|
1055
|
-
first = false;
|
|
1056
|
-
} else {
|
|
1057
|
-
const response2 = await client.continueSession(
|
|
1058
|
-
message.message,
|
|
1059
|
-
{ signal: abortController.signal }
|
|
1060
|
-
);
|
|
1061
|
-
logger.debug("[Codex] continueSession response:", response2);
|
|
1062
|
-
}
|
|
1063
|
-
} catch (error) {
|
|
1064
|
-
logger.warn("Error in codex session:", error);
|
|
1065
|
-
const isAbortError = error instanceof Error && error.name === "AbortError";
|
|
1066
|
-
if (isAbortError) {
|
|
1067
|
-
messageBuffer.addMessage("Aborted by user", "status");
|
|
1068
|
-
session.sendSessionEvent({ type: "message", message: "Aborted by user" });
|
|
1069
|
-
} else {
|
|
1070
|
-
messageBuffer.addMessage("Process exited unexpectedly", "status");
|
|
1071
|
-
session.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
|
|
1072
|
-
if (client.hasActiveSession()) {
|
|
1073
|
-
storedSessionIdForResume = client.storeSessionForResume();
|
|
1074
|
-
logger.debug("[Codex] Stored session after unexpected error:", storedSessionIdForResume);
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
} finally {
|
|
1078
|
-
permissionHandler.reset();
|
|
1079
|
-
reasoningProcessor.abort();
|
|
1080
|
-
diffProcessor.reset();
|
|
1081
|
-
thinking = false;
|
|
1082
|
-
session.keepAlive(thinking, "remote");
|
|
1083
|
-
emitReadyIfIdle({
|
|
1084
|
-
pending,
|
|
1085
|
-
queueSize: () => messageQueue.size(),
|
|
1086
|
-
shouldExit,
|
|
1087
|
-
sendReady
|
|
1088
|
-
});
|
|
1089
|
-
logActiveHandles("after-turn");
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
} finally {
|
|
1093
|
-
logger.debug("[codex]: Final cleanup start");
|
|
1094
|
-
logActiveHandles("cleanup-start");
|
|
1095
|
-
if (reconnectionHandle) {
|
|
1096
|
-
logger.debug("[codex]: Cancelling offline reconnection");
|
|
1097
|
-
reconnectionHandle.cancel();
|
|
1098
|
-
}
|
|
1099
|
-
try {
|
|
1100
|
-
logger.debug("[codex]: sendSessionDeath");
|
|
1101
|
-
session.sendSessionDeath();
|
|
1102
|
-
logger.debug("[codex]: flush begin");
|
|
1103
|
-
await session.flush();
|
|
1104
|
-
logger.debug("[codex]: flush done");
|
|
1105
|
-
logger.debug("[codex]: session.close begin");
|
|
1106
|
-
await session.close();
|
|
1107
|
-
logger.debug("[codex]: session.close done");
|
|
1108
|
-
} catch (e) {
|
|
1109
|
-
logger.debug("[codex]: Error while closing session", e);
|
|
1110
|
-
}
|
|
1111
|
-
logger.debug("[codex]: client.forceCloseSession begin");
|
|
1112
|
-
await client.forceCloseSession();
|
|
1113
|
-
logger.debug("[codex]: client.forceCloseSession done");
|
|
1114
|
-
if (process.stdin.isTTY) {
|
|
1115
|
-
logger.debug("[codex]: setRawMode(false)");
|
|
1116
|
-
try {
|
|
1117
|
-
process.stdin.setRawMode(false);
|
|
1118
|
-
} catch {
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
if (hasTTY) {
|
|
1122
|
-
logger.debug("[codex]: stdin.pause()");
|
|
1123
|
-
try {
|
|
1124
|
-
process.stdin.pause();
|
|
1125
|
-
} catch {
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
logger.debug("[codex]: clearInterval(keepAlive)");
|
|
1129
|
-
clearInterval(keepAliveInterval);
|
|
1130
|
-
if (inkInstance) {
|
|
1131
|
-
logger.debug("[codex]: inkInstance.unmount()");
|
|
1132
|
-
inkInstance.unmount();
|
|
1133
|
-
}
|
|
1134
|
-
messageBuffer.clear();
|
|
1135
|
-
logActiveHandles("cleanup-end");
|
|
1136
|
-
logger.debug("[codex]: Final cleanup completed");
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
export { emitReadyIfIdle, runCodex };
|