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.
Files changed (36) hide show
  1. package/dist/{setupOfflineReconnection-ndObLZk0.mjs → BaseReasoningProcessor-BKLRCKTU.mjs} +133 -90
  2. package/dist/{setupOfflineReconnection-obypStdD.cjs → BaseReasoningProcessor-BRCQXCZY.cjs} +134 -90
  3. package/dist/{types-BXyraW9R.mjs → api-BGXYX0yH.mjs} +198 -170
  4. package/dist/{types-BSTmyv9d.cjs → api-D7OK-mML.cjs} +219 -192
  5. package/dist/command-CnLtKtP-.mjs +51 -0
  6. package/dist/command-G85giEAF.cjs +54 -0
  7. package/dist/future-Dq4Ha1Dn.cjs +24 -0
  8. package/dist/future-xRdLl3vf.mjs +22 -0
  9. package/dist/{index-DVI4b0mv.cjs → index-B_wlQBy2.cjs} +5493 -7142
  10. package/dist/{index-CUmYqKWt.mjs → index-C7Y0R-MI.mjs} +5482 -7143
  11. package/dist/index.cjs +19 -21
  12. package/dist/index.mjs +19 -21
  13. package/dist/lib.cjs +3 -2
  14. package/dist/lib.d.cts +17 -0
  15. package/dist/lib.d.mts +17 -0
  16. package/dist/lib.mjs +2 -1
  17. package/dist/{persistence-BGsuPqaO.mjs → persistence-BA_unuca.mjs} +8 -4
  18. package/dist/{persistence-BRH9F6RS.cjs → persistence-DHgf1CTG.cjs} +10 -6
  19. package/dist/registerKillSessionHandler-C2-yHm1V.mjs +428 -0
  20. package/dist/registerKillSessionHandler-CLREXN11.cjs +433 -0
  21. package/dist/runClaude-CwAitpX-.cjs +3274 -0
  22. package/dist/runClaude-uNC5Eym4.mjs +3271 -0
  23. package/dist/runCodex-B-05E-YZ.mjs +1846 -0
  24. package/dist/runCodex-Cm0VTqw_.cjs +1848 -0
  25. package/dist/{runGemini-C3dDtGOV.cjs → runGemini-CLWjwDYS.cjs} +25 -1366
  26. package/dist/{runGemini-B-EK_BJQ.mjs → runGemini-_biXvQAH.mjs} +12 -1353
  27. package/dist/types-CiliQpqS.mjs +52 -0
  28. package/dist/types-DVk3crez.cjs +54 -0
  29. package/package.json +13 -12
  30. package/scripts/devtools/README.md +9 -0
  31. package/scripts/devtools/generate-mock-credentials.ts +94 -0
  32. package/scripts/release-smoke.mjs +62 -0
  33. package/dist/config-BQNrtwRY.cjs +0 -183
  34. package/dist/config-Dn99YH37.mjs +0 -173
  35. package/dist/runCodex-Cez8cuIh.cjs +0 -1143
  36. package/dist/runCodex-X0BfjcZH.mjs +0 -1140
@@ -0,0 +1,1848 @@
1
+ 'use strict';
2
+
3
+ var node_crypto = require('node:crypto');
4
+ var api = require('./api-D7OK-mML.cjs');
5
+ var persistence = require('./persistence-DHgf1CTG.cjs');
6
+ var index = require('./index-B_wlQBy2.cjs');
7
+ var BaseReasoningProcessor = require('./BaseReasoningProcessor-BRCQXCZY.cjs');
8
+ var registerKillSessionHandler = require('./registerKillSessionHandler-CLREXN11.cjs');
9
+ var future = require('./future-Dq4Ha1Dn.cjs');
10
+ var node_child_process = require('node:child_process');
11
+ var fs = require('node:fs');
12
+ var os = require('node:os');
13
+ var path = require('node:path');
14
+ var React = require('react');
15
+ var ink = require('ink');
16
+ var index_js = require('@modelcontextprotocol/sdk/client/index.js');
17
+ var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
18
+ var z = require('zod');
19
+ var types_js = require('@modelcontextprotocol/sdk/types.js');
20
+ var child_process = require('child_process');
21
+ require('axios');
22
+ require('chalk');
23
+ require('fs');
24
+ require('node:events');
25
+ require('socket.io-client');
26
+ require('tweetnacl');
27
+ require('util');
28
+ require('fs/promises');
29
+ require('crypto');
30
+ require('path');
31
+ require('expo-server-sdk');
32
+ require('node:fs/promises');
33
+ require('os');
34
+ require('tmp');
35
+ require('qrcode-terminal');
36
+ require('node:module');
37
+ require('open');
38
+ require('url');
39
+ require('ps-list');
40
+ require('cross-spawn');
41
+ require('fastify');
42
+ require('fastify-type-provider-zod');
43
+ require('node:readline');
44
+ require('http');
45
+ require('@agentclientprotocol/sdk');
46
+ require('./types-DVk3crez.cjs');
47
+
48
+ const DEFAULT_CAPABILITIES = {
49
+ committedMessageAcks: false,
50
+ pendingQueueV2: false,
51
+ userScopedSocket: false,
52
+ snapshotChangeCatchup: false,
53
+ structuredSpawnErrors: false,
54
+ metadataWatch: false
55
+ };
56
+ function normalizeHappyUserMessage(message) {
57
+ return {
58
+ text: message.content.text,
59
+ permissionMode: message.meta?.permissionMode,
60
+ model: message.meta?.model ?? void 0,
61
+ localId: message.localKey ?? null,
62
+ raw: message
63
+ };
64
+ }
65
+ function createLegacyHappyCodexSessionAdapter(options) {
66
+ let metadataSnapshot = options.initialMetadata ?? options.session.getMetadataSnapshot?.() ?? null;
67
+ let agentStateSnapshot = options.initialAgentState ?? options.session.getAgentStateSnapshot?.() ?? null;
68
+ return {
69
+ sessionId: options.session.sessionId,
70
+ capabilities: DEFAULT_CAPABILITIES,
71
+ onUserMessage(callback) {
72
+ options.session.onUserMessage((message) => {
73
+ callback(normalizeHappyUserMessage(message));
74
+ });
75
+ },
76
+ sendCodexMessage(body) {
77
+ options.session.sendCodexMessage(body);
78
+ },
79
+ sendAgentMessage(body) {
80
+ options.session.sendAgentMessage("codex", body);
81
+ },
82
+ sendSessionEvent(event, id) {
83
+ options.session.sendSessionEvent(event, id);
84
+ },
85
+ keepAlive(thinking, mode) {
86
+ options.session.keepAlive(thinking, mode);
87
+ },
88
+ sendSessionDeath() {
89
+ options.session.sendSessionDeath();
90
+ },
91
+ updateMetadata(handler) {
92
+ options.session.updateMetadata((current) => {
93
+ const next = handler(current);
94
+ metadataSnapshot = next;
95
+ return next;
96
+ });
97
+ },
98
+ updateAgentState(handler) {
99
+ options.session.updateAgentState((current) => {
100
+ const next = handler(current);
101
+ agentStateSnapshot = next;
102
+ return next;
103
+ });
104
+ },
105
+ getMetadataSnapshot() {
106
+ return metadataSnapshot;
107
+ },
108
+ getAgentStateSnapshot() {
109
+ return agentStateSnapshot;
110
+ },
111
+ async waitForMetadataUpdate(signal) {
112
+ const metadata = await (options.session.waitForMetadataUpdate?.(signal) ?? Promise.resolve(metadataSnapshot));
113
+ metadataSnapshot = metadata;
114
+ return metadata;
115
+ },
116
+ async flush() {
117
+ await options.session.flush();
118
+ },
119
+ async close() {
120
+ await options.session.close();
121
+ }
122
+ };
123
+ }
124
+
125
+ class CodexSession {
126
+ path;
127
+ logPath;
128
+ api;
129
+ client;
130
+ runtimeSession;
131
+ queue;
132
+ codexArgs;
133
+ sessionId;
134
+ mode;
135
+ thinking = false;
136
+ keepAliveInterval;
137
+ onModeChangeCallback;
138
+ clientSwapCallbacks = [];
139
+ supersedePendingInteractionsCallback = null;
140
+ constructor(opts) {
141
+ this.path = opts.path;
142
+ this.api = opts.api;
143
+ this.client = opts.client;
144
+ this.runtimeSession = createLegacyHappyCodexSessionAdapter({
145
+ session: opts.client
146
+ });
147
+ this.logPath = opts.logPath;
148
+ this.sessionId = opts.sessionId;
149
+ this.mode = opts.mode;
150
+ this.queue = opts.messageQueue;
151
+ this.codexArgs = opts.codexArgs;
152
+ this.onModeChangeCallback = opts.onModeChange;
153
+ this.client.keepAlive(this.thinking, this.mode);
154
+ this.keepAliveInterval = setInterval(() => {
155
+ this.client.keepAlive(this.thinking, this.mode);
156
+ }, 2e3);
157
+ }
158
+ cleanup() {
159
+ clearInterval(this.keepAliveInterval);
160
+ api.logger.debug("[CodexSession] Cleaned up resources");
161
+ }
162
+ updateClient = (client) => {
163
+ this.client = client;
164
+ this.runtimeSession = createLegacyHappyCodexSessionAdapter({
165
+ session: client
166
+ });
167
+ for (const callback of this.clientSwapCallbacks) {
168
+ callback(client);
169
+ }
170
+ };
171
+ addClientSwapCallback = (callback) => {
172
+ this.clientSwapCallbacks.push(callback);
173
+ };
174
+ removeClientSwapCallback = (callback) => {
175
+ const index = this.clientSwapCallbacks.indexOf(callback);
176
+ if (index !== -1) {
177
+ this.clientSwapCallbacks.splice(index, 1);
178
+ }
179
+ };
180
+ setPendingInteractionSuperseder = (callback) => {
181
+ this.supersedePendingInteractionsCallback = callback;
182
+ };
183
+ supersedePendingInteractions = (reason) => {
184
+ return this.supersedePendingInteractionsCallback?.(reason) ?? false;
185
+ };
186
+ onThinkingChange = (thinking) => {
187
+ this.thinking = thinking;
188
+ this.client.keepAlive(thinking, this.mode);
189
+ };
190
+ onModeChange = (mode) => {
191
+ this.mode = mode;
192
+ this.client.keepAlive(this.thinking, mode);
193
+ this.onModeChangeCallback(mode);
194
+ };
195
+ onSessionFound = (sessionId) => {
196
+ if (this.sessionId === sessionId) {
197
+ return;
198
+ }
199
+ this.sessionId = sessionId;
200
+ this.client.updateMetadata((metadata) => ({
201
+ ...metadata,
202
+ codexSessionId: sessionId
203
+ }));
204
+ api.logger.debug(`[CodexSession] Session ID ${sessionId} added to metadata`);
205
+ };
206
+ clearSessionId = () => {
207
+ this.sessionId = null;
208
+ api.logger.debug("[CodexSession] Session ID cleared");
209
+ };
210
+ }
211
+
212
+ function getCodexSessionsRoot() {
213
+ const codexHomeDir = process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
214
+ return path.join(codexHomeDir, "sessions");
215
+ }
216
+ function collectFilesRecursive(dir, acc = []) {
217
+ let entries;
218
+ try {
219
+ entries = fs.readdirSync(dir, { withFileTypes: true });
220
+ } catch {
221
+ return acc;
222
+ }
223
+ for (const entry of entries) {
224
+ const full = path.join(dir, entry.name);
225
+ if (entry.isDirectory()) {
226
+ collectFilesRecursive(full, acc);
227
+ } else if (entry.isFile()) {
228
+ acc.push(full);
229
+ }
230
+ }
231
+ return acc;
232
+ }
233
+ function extractCodexSessionIdFromPath(filePath) {
234
+ const match = filePath.match(/-([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\.jsonl$/);
235
+ return match?.[1] ?? null;
236
+ }
237
+ function findCodexResumeFile(sessionId) {
238
+ if (!sessionId) return null;
239
+ try {
240
+ const candidates = collectFilesRecursive(getCodexSessionsRoot()).filter((full) => full.endsWith(`-${sessionId}.jsonl`)).filter((full) => {
241
+ try {
242
+ return fs.statSync(full).isFile();
243
+ } catch {
244
+ return false;
245
+ }
246
+ }).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
247
+ return candidates[0] || null;
248
+ } catch {
249
+ return null;
250
+ }
251
+ }
252
+ function findLatestCodexSessionIdSince(startTimeMs) {
253
+ try {
254
+ const candidates = collectFilesRecursive(getCodexSessionsRoot()).filter((full) => full.endsWith(".jsonl")).filter((full) => {
255
+ try {
256
+ return fs.statSync(full).mtimeMs >= startTimeMs;
257
+ } catch {
258
+ return false;
259
+ }
260
+ }).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
261
+ for (const candidate of candidates) {
262
+ const sessionId = extractCodexSessionIdFromPath(candidate);
263
+ if (sessionId) {
264
+ return sessionId;
265
+ }
266
+ }
267
+ } catch {
268
+ return null;
269
+ }
270
+ return null;
271
+ }
272
+
273
+ class CodexExitCodeError extends Error {
274
+ exitCode;
275
+ constructor(exitCode) {
276
+ super(`Codex process exited with code: ${exitCode}`);
277
+ this.name = "CodexExitCodeError";
278
+ this.exitCode = exitCode;
279
+ }
280
+ }
281
+ function terminateCodexLocalChild(pid) {
282
+ if (!pid) {
283
+ return;
284
+ }
285
+ try {
286
+ if (process.platform === "win32") {
287
+ node_child_process.execFileSync("taskkill", ["/F", "/T", "/PID", pid.toString()], { stdio: "ignore" });
288
+ return;
289
+ }
290
+ process.kill(pid, "SIGTERM");
291
+ } catch (error) {
292
+ api.logger.debug("[CodexLocal] Failed to terminate child process", error);
293
+ }
294
+ }
295
+ async function codexLocal(opts) {
296
+ const baseArgs = [...opts.codexArgs ?? []];
297
+ const args = opts.sessionId ? ["resume", opts.sessionId, ...baseArgs] : baseArgs;
298
+ const startTime = Date.now();
299
+ let detectedSessionId = opts.sessionId;
300
+ const codexExecutable = index.resolveCodexExecutable();
301
+ api.logger.debug(`[CodexLocal] Spawning ${codexExecutable} with args: ${JSON.stringify(args)}`);
302
+ process.stdin.pause();
303
+ try {
304
+ await new Promise((resolve, reject) => {
305
+ const child = node_child_process.spawn(codexExecutable, args, {
306
+ cwd: opts.path,
307
+ env: process.env,
308
+ stdio: "inherit",
309
+ signal: opts.abort,
310
+ shell: index.shouldUseShellForCodex(codexExecutable)
311
+ });
312
+ const abortChild = () => {
313
+ if (child.exitCode === null) {
314
+ api.logger.debug(`[CodexLocal] Abort received, terminating child tree pid=${child.pid ?? "none"}`);
315
+ terminateCodexLocalChild(child.pid);
316
+ }
317
+ };
318
+ opts.abort.addEventListener("abort", abortChild, { once: true });
319
+ const sessionPoll = setInterval(() => {
320
+ if (detectedSessionId) {
321
+ return;
322
+ }
323
+ const nextSessionId = findLatestCodexSessionIdSince(startTime);
324
+ if (nextSessionId) {
325
+ detectedSessionId = nextSessionId;
326
+ api.logger.debug(`[CodexLocal] Detected session ID: ${nextSessionId}`);
327
+ opts.onSessionFound(nextSessionId);
328
+ }
329
+ }, 1e3);
330
+ child.on("error", (error) => {
331
+ clearInterval(sessionPoll);
332
+ opts.abort.removeEventListener("abort", abortChild);
333
+ reject(error);
334
+ });
335
+ child.on("exit", (code, signal) => {
336
+ clearInterval(sessionPoll);
337
+ opts.abort.removeEventListener("abort", abortChild);
338
+ opts.onThinkingChange?.(false);
339
+ if (signal === "SIGTERM" && opts.abort.aborted) {
340
+ resolve();
341
+ return;
342
+ }
343
+ if (signal) {
344
+ reject(new Error(`Codex terminated with signal: ${signal}`));
345
+ return;
346
+ }
347
+ if (code !== 0 && code !== null) {
348
+ reject(new CodexExitCodeError(code));
349
+ return;
350
+ }
351
+ resolve();
352
+ });
353
+ });
354
+ } finally {
355
+ process.stdin.resume();
356
+ opts.onThinkingChange?.(false);
357
+ }
358
+ return detectedSessionId;
359
+ }
360
+
361
+ async function codexLocalLauncher(session) {
362
+ let exitReason = null;
363
+ const processAbortController = new AbortController();
364
+ const exitFuture = new future.Future();
365
+ try {
366
+ async function abort() {
367
+ if (!processAbortController.signal.aborted) {
368
+ processAbortController.abort();
369
+ }
370
+ await exitFuture.promise;
371
+ }
372
+ async function doSwitch() {
373
+ api.logger.debug("[codex-local]: switching to remote mode");
374
+ if (!exitReason) {
375
+ exitReason = { type: "switch" };
376
+ }
377
+ await abort();
378
+ }
379
+ async function doAbort() {
380
+ api.logger.debug("[codex-local]: abort requested");
381
+ if (!exitReason) {
382
+ exitReason = { type: "switch" };
383
+ }
384
+ session.queue.reset();
385
+ await abort();
386
+ }
387
+ session.client.rpcHandlerManager.registerHandler("abort", doAbort);
388
+ session.client.rpcHandlerManager.registerHandler("switch", doSwitch);
389
+ session.queue.setOnMessage(() => {
390
+ void doSwitch();
391
+ });
392
+ if (session.queue.size() > 0) {
393
+ return { type: "switch" };
394
+ }
395
+ while (true) {
396
+ if (exitReason) {
397
+ return exitReason;
398
+ }
399
+ try {
400
+ const sessionId = await codexLocal({
401
+ path: session.path,
402
+ sessionId: session.sessionId,
403
+ abort: processAbortController.signal,
404
+ codexArgs: session.codexArgs,
405
+ onSessionFound: session.onSessionFound,
406
+ onThinkingChange: session.onThinkingChange
407
+ });
408
+ if (sessionId) {
409
+ session.onSessionFound(sessionId);
410
+ }
411
+ if (!exitReason) {
412
+ exitReason = { type: "exit", code: 0 };
413
+ break;
414
+ }
415
+ } catch (error) {
416
+ api.logger.debug("[codex-local]: launch error", error);
417
+ if (error instanceof CodexExitCodeError && !exitReason) {
418
+ exitReason = { type: "exit", code: error.exitCode };
419
+ break;
420
+ }
421
+ if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "EINVAL") && !exitReason) {
422
+ exitReason = { type: "exit", code: 1 };
423
+ session.runtimeSession.sendSessionEvent({ type: "message", message: "Codex CLI not found. Check your local Codex installation." });
424
+ break;
425
+ }
426
+ if (!exitReason) {
427
+ session.runtimeSession.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
428
+ continue;
429
+ }
430
+ break;
431
+ }
432
+ }
433
+ } finally {
434
+ exitFuture.resolve(void 0);
435
+ session.client.rpcHandlerManager.registerHandler("abort", async () => {
436
+ });
437
+ session.client.rpcHandlerManager.registerHandler("switch", async () => {
438
+ });
439
+ session.queue.setOnMessage(null);
440
+ }
441
+ return exitReason || { type: "exit", code: 0 };
442
+ }
443
+
444
+ const CodexDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal, title }) => {
445
+ const [messages, setMessages] = React.useState([]);
446
+ const [confirmationMode, setConfirmationMode] = React.useState(null);
447
+ const [actionInProgress, setActionInProgress] = React.useState(null);
448
+ const confirmationTimeoutRef = React.useRef(null);
449
+ const { stdout } = ink.useStdout();
450
+ const terminalWidth = stdout.columns || 80;
451
+ const terminalHeight = stdout.rows || 24;
452
+ React.useEffect(() => {
453
+ setMessages(messageBuffer.getMessages());
454
+ const unsubscribe = messageBuffer.onUpdate((newMessages) => {
455
+ setMessages(newMessages);
456
+ });
457
+ return () => {
458
+ unsubscribe();
459
+ if (confirmationTimeoutRef.current) {
460
+ clearTimeout(confirmationTimeoutRef.current);
461
+ }
462
+ };
463
+ }, [messageBuffer]);
464
+ const resetConfirmation = React.useCallback(() => {
465
+ setConfirmationMode(null);
466
+ if (confirmationTimeoutRef.current) {
467
+ clearTimeout(confirmationTimeoutRef.current);
468
+ confirmationTimeoutRef.current = null;
469
+ }
470
+ }, []);
471
+ const setConfirmationWithTimeout = React.useCallback((mode) => {
472
+ setConfirmationMode(mode);
473
+ if (confirmationTimeoutRef.current) {
474
+ clearTimeout(confirmationTimeoutRef.current);
475
+ }
476
+ confirmationTimeoutRef.current = setTimeout(() => {
477
+ resetConfirmation();
478
+ }, 15e3);
479
+ }, [resetConfirmation]);
480
+ ink.useInput(React.useCallback(async (input, key) => {
481
+ if (actionInProgress) return;
482
+ if (key.ctrl && input === "c") {
483
+ if (confirmationMode === "exit") {
484
+ resetConfirmation();
485
+ setActionInProgress("exiting");
486
+ await new Promise((resolve) => setTimeout(resolve, 100));
487
+ onExit?.();
488
+ } else {
489
+ setConfirmationWithTimeout("exit");
490
+ }
491
+ return;
492
+ }
493
+ if (input === " ") {
494
+ if (confirmationMode === "switch") {
495
+ resetConfirmation();
496
+ setActionInProgress("switching");
497
+ await new Promise((resolve) => setTimeout(resolve, 100));
498
+ onSwitchToLocal?.();
499
+ } else {
500
+ setConfirmationWithTimeout("switch");
501
+ }
502
+ return;
503
+ }
504
+ if (confirmationMode) {
505
+ resetConfirmation();
506
+ }
507
+ }, [confirmationMode, actionInProgress, onExit, onSwitchToLocal, setConfirmationWithTimeout, resetConfirmation]));
508
+ const getMessageColor = (type) => {
509
+ switch (type) {
510
+ case "user":
511
+ return "magenta";
512
+ case "assistant":
513
+ return "cyan";
514
+ case "system":
515
+ return "blue";
516
+ case "tool":
517
+ return "yellow";
518
+ case "result":
519
+ return "green";
520
+ case "status":
521
+ return "gray";
522
+ default:
523
+ return "white";
524
+ }
525
+ };
526
+ const formatMessage = (msg) => {
527
+ const lines = msg.content.split("\n");
528
+ const maxLineLength = terminalWidth - 10;
529
+ return lines.map((line) => {
530
+ if (line.length <= maxLineLength) return line;
531
+ const chunks = [];
532
+ for (let i = 0; i < line.length; i += maxLineLength) {
533
+ chunks.push(line.slice(i, i + maxLineLength));
534
+ }
535
+ return chunks.join("\n");
536
+ }).join("\n");
537
+ };
538
+ return /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", width: terminalWidth, height: terminalHeight }, /* @__PURE__ */ React.createElement(
539
+ ink.Box,
540
+ {
541
+ flexDirection: "column",
542
+ width: terminalWidth,
543
+ height: terminalHeight - 4,
544
+ borderStyle: "round",
545
+ borderColor: "gray",
546
+ paddingX: 1,
547
+ overflow: "hidden"
548
+ },
549
+ /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, title || "\u{1F916} Codex Agent Messages"), /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "\u2500".repeat(Math.min(terminalWidth - 4, 60)))),
550
+ /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", height: terminalHeight - 10, overflow: "hidden" }, messages.length === 0 ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Waiting for messages...") : (
551
+ // Show only the last messages that fit in the available space
552
+ messages.slice(-Math.max(1, terminalHeight - 10)).map((msg) => /* @__PURE__ */ React.createElement(ink.Box, { key: msg.id, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { color: getMessageColor(msg.type), dimColor: true }, formatMessage(msg))))
553
+ ))
554
+ ), /* @__PURE__ */ React.createElement(
555
+ ink.Box,
556
+ {
557
+ width: terminalWidth,
558
+ borderStyle: "round",
559
+ borderColor: actionInProgress ? "gray" : confirmationMode === "exit" ? "red" : confirmationMode === "switch" ? "yellow" : "green",
560
+ paddingX: 2,
561
+ justifyContent: "center",
562
+ alignItems: "center",
563
+ flexDirection: "column"
564
+ },
565
+ /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", alignItems: "center" }, actionInProgress === "exiting" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "Exiting agent...") : actionInProgress === "switching" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "Switching to local mode...") : confirmationMode === "exit" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "red", bold: true }, "\u26A0\uFE0F Press Ctrl-C again to exit the agent") : confirmationMode === "switch" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "yellow", bold: true }, "\u23F8\uFE0F Press space again to switch to local mode") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ink.Text, { color: "green", bold: true }, "\u{1F916} Codex Agent Running \u2022 Press space for local \u2022 Ctrl-C to exit")), process.env.DEBUG && logPath && /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Debug logs: ", logPath))
566
+ ));
567
+ };
568
+
569
+ const DEFAULT_TIMEOUT = 14 * 24 * 60 * 60 * 1e3;
570
+ function getCodexMcpCommand() {
571
+ const codexExecutable = index.resolveCodexExecutable();
572
+ try {
573
+ const version = child_process.execSync(`"${codexExecutable}" --version`, { encoding: "utf8" }).trim();
574
+ const match = version.match(/codex-cli\s+(\d+\.\d+\.\d+(?:-alpha\.\d+)?)/);
575
+ if (!match) {
576
+ api.logger.debug("[CodexMCP] Could not parse codex version:", version);
577
+ return null;
578
+ }
579
+ const versionStr = match[1];
580
+ const [major, minor, patch] = versionStr.split(/[-.]/).map(Number);
581
+ if (major > 0 || minor > 43) return "mcp-server";
582
+ if (minor === 43 && patch === 0) {
583
+ if (versionStr.includes("-alpha.")) {
584
+ const alphaNum = parseInt(versionStr.split("-alpha.")[1]);
585
+ return alphaNum >= 5 ? "mcp-server" : "mcp";
586
+ }
587
+ return "mcp-server";
588
+ }
589
+ return "mcp";
590
+ } catch (error) {
591
+ api.logger.debug("[CodexMCP] Codex CLI not found or not executable:", error);
592
+ return null;
593
+ }
594
+ }
595
+ function resolveCodexMcpTransport(executable, mcpCommand) {
596
+ return {
597
+ // Preserve the resolved executable path so daemon/service environments do not
598
+ // depend on APPDATA npm shims being present on PATH.
599
+ command: executable,
600
+ args: [mcpCommand]
601
+ };
602
+ }
603
+ function extractCodexIdentifiers(response) {
604
+ const meta = response?.meta || {};
605
+ const structured = response?.structuredContent || response?.structured_content || response?.structured_output || {};
606
+ const sessionId = meta.sessionId || meta.session_id || response?.sessionId || response?.session_id || meta.threadId || meta.thread_id || response?.threadId || response?.thread_id || structured.threadId || structured.thread_id || null;
607
+ const conversationId = meta.conversationId || meta.conversation_id || response?.conversationId || response?.conversation_id || meta.threadId || meta.thread_id || response?.threadId || response?.thread_id || structured.threadId || structured.thread_id || structured.conversationId || structured.conversation_id || null;
608
+ return { sessionId, conversationId };
609
+ }
610
+ function normalizeCodexPermissionRequest(params) {
611
+ return {
612
+ requestId: params.codex_call_id || params.call_id || params.codex_mcp_tool_call_id || params.codex_event_id || `codex-permission-${Date.now()}`,
613
+ toolName: "CodexBash",
614
+ input: {
615
+ command: params.codex_command || params.command,
616
+ cwd: params.codex_cwd || params.cwd,
617
+ proposed_execpolicy_amendment: params.proposed_execpolicy_amendment,
618
+ parsed_cmd: params.parsed_cmd
619
+ }
620
+ };
621
+ }
622
+ function resolveCodexPermissionRequest(params, fallback) {
623
+ const normalized = normalizeCodexPermissionRequest(params);
624
+ const hasInlinePayload = Boolean(
625
+ normalized.input.command || normalized.input.cwd || normalized.input.proposed_execpolicy_amendment || normalized.input.parsed_cmd
626
+ );
627
+ const hasInlineId = Boolean(
628
+ params.codex_call_id || params.call_id || params.codex_mcp_tool_call_id || params.codex_event_id
629
+ );
630
+ if (!fallback || hasInlinePayload && hasInlineId) {
631
+ return normalized;
632
+ }
633
+ return {
634
+ requestId: hasInlineId ? normalized.requestId : fallback.call_id || normalized.requestId,
635
+ toolName: normalized.toolName,
636
+ input: {
637
+ command: normalized.input.command || fallback.command,
638
+ cwd: normalized.input.cwd || fallback.cwd,
639
+ proposed_execpolicy_amendment: normalized.input.proposed_execpolicy_amendment || fallback.proposed_execpolicy_amendment,
640
+ parsed_cmd: normalized.input.parsed_cmd || fallback.parsed_cmd
641
+ }
642
+ };
643
+ }
644
+ class CodexMcpClient {
645
+ client;
646
+ transport = null;
647
+ connected = false;
648
+ sessionId = null;
649
+ conversationId = null;
650
+ handler = null;
651
+ permissionHandler = null;
652
+ selectionHandler = null;
653
+ pendingExecApprovalContexts = [];
654
+ constructor() {
655
+ this.client = this.createClient();
656
+ }
657
+ createClient() {
658
+ const client = new index_js.Client(
659
+ { name: "happy-codex-client", version: "1.0.0" },
660
+ { capabilities: { elicitation: {} } }
661
+ );
662
+ client.setNotificationHandler(z.z.object({
663
+ method: z.z.literal("codex/event"),
664
+ params: z.z.object({
665
+ msg: z.z.any()
666
+ })
667
+ }).passthrough(), (data) => {
668
+ const msg = data.params.msg;
669
+ if (msg?.type === "exec_approval_request") {
670
+ this.pendingExecApprovalContexts.push({
671
+ call_id: msg.call_id,
672
+ command: msg.command,
673
+ cwd: msg.cwd,
674
+ proposed_execpolicy_amendment: msg.proposed_execpolicy_amendment,
675
+ parsed_cmd: msg.parsed_cmd
676
+ });
677
+ }
678
+ this.updateIdentifiersFromEvent(msg);
679
+ this.handler?.(msg);
680
+ });
681
+ return client;
682
+ }
683
+ setHandler(handler) {
684
+ this.handler = handler;
685
+ }
686
+ /**
687
+ * Set the permission handler for tool approval
688
+ */
689
+ setPermissionHandler(handler) {
690
+ this.permissionHandler = handler;
691
+ }
692
+ setSelectionHandler(handler) {
693
+ this.selectionHandler = handler;
694
+ }
695
+ async connect() {
696
+ if (this.connected) return;
697
+ const mcpCommand = getCodexMcpCommand();
698
+ if (mcpCommand === null) {
699
+ throw new Error(
700
+ "Codex CLI not found or not executable.\n\nTo install codex:\n npm install -g @openai/codex\n\nAlternatively, use Claude:\n happy claude"
701
+ );
702
+ }
703
+ api.logger.debug(`[CodexMCP] Connecting to Codex MCP server using command: codex ${mcpCommand}`);
704
+ const codexExecutable = index.resolveCodexExecutable();
705
+ const transport = resolveCodexMcpTransport(codexExecutable, mcpCommand);
706
+ this.transport = new stdio_js.StdioClientTransport({
707
+ command: transport.command,
708
+ args: transport.args,
709
+ env: Object.keys(process.env).reduce((acc, key) => {
710
+ const value = process.env[key];
711
+ if (typeof value === "string") acc[key] = value;
712
+ return acc;
713
+ }, {})
714
+ });
715
+ this.registerPermissionHandlers();
716
+ await this.client.connect(this.transport);
717
+ this.connected = true;
718
+ api.logger.debug("[CodexMCP] Connected to Codex");
719
+ }
720
+ isClosedTransportError(error) {
721
+ const record = error;
722
+ const code = typeof record?.code === "number" ? record.code : null;
723
+ if (code === -32e3) {
724
+ return true;
725
+ }
726
+ const message = typeof record?.message === "string" ? record.message.toLowerCase() : "";
727
+ if (code === -32001 && message.includes("aborterror")) {
728
+ return true;
729
+ }
730
+ return message.includes("connection closed") || message.includes("not connected");
731
+ }
732
+ async callToolWithReconnectRetry(call) {
733
+ try {
734
+ return await call();
735
+ } catch (error) {
736
+ if (!this.isClosedTransportError(error)) {
737
+ throw error;
738
+ }
739
+ api.logger.debug("[CodexMCP] Transport closed during tool call; reconnecting and retrying once");
740
+ try {
741
+ await this.disconnect();
742
+ } catch {
743
+ }
744
+ await this.connect();
745
+ return await call();
746
+ }
747
+ }
748
+ registerPermissionHandlers() {
749
+ this.client.setRequestHandler(
750
+ types_js.ElicitRequestSchema,
751
+ async (request) => {
752
+ const params = request.params;
753
+ api.logger.debug("[CodexMCP] Received elicitation request:", params);
754
+ try {
755
+ if (this.isSelectionRequest(params)) {
756
+ return await this.handleSelectionRequest(params);
757
+ }
758
+ if (!this.permissionHandler) {
759
+ api.logger.debug("[CodexMCP] No permission handler set, denying by default");
760
+ return {
761
+ action: "decline",
762
+ decision: "denied",
763
+ content: {
764
+ decision: "denied"
765
+ }
766
+ };
767
+ }
768
+ const normalized = resolveCodexPermissionRequest(
769
+ params,
770
+ this.takeExecApprovalContextForPermission(params)
771
+ );
772
+ const result = await this.permissionHandler.handleToolCall(
773
+ normalized.requestId,
774
+ normalized.toolName,
775
+ normalized.input
776
+ );
777
+ api.logger.debug("[CodexMCP] Permission result:", result);
778
+ return {
779
+ action: result.decision === "abort" ? "cancel" : result.decision === "denied" ? "decline" : "accept",
780
+ decision: result.decision,
781
+ content: {
782
+ decision: result.decision
783
+ }
784
+ };
785
+ } catch (error) {
786
+ if (error instanceof Error && error.message === "Session reset") {
787
+ api.logger.debug("[CodexMCP] Permission request cancelled during session reset");
788
+ return {
789
+ action: "cancel"
790
+ };
791
+ }
792
+ if (error instanceof Error && error.message === BaseReasoningProcessor.INTERACTION_SUPERSEDED_ERROR) {
793
+ api.logger.debug("[CodexMCP] Selection request cancelled because a new user message arrived");
794
+ return {
795
+ action: "cancel"
796
+ };
797
+ }
798
+ api.logger.debug("[CodexMCP] Error handling permission request:", error);
799
+ return {
800
+ action: "decline",
801
+ decision: "denied",
802
+ reason: error instanceof Error ? error.message : "Permission request failed"
803
+ };
804
+ }
805
+ }
806
+ );
807
+ api.logger.debug("[CodexMCP] Permission handlers registered");
808
+ }
809
+ isSelectionRequest(params) {
810
+ if (Array.isArray(params.codex_command) && params.codex_command.length > 0) {
811
+ return false;
812
+ }
813
+ if (Array.isArray(params.options) && params.options.length > 0) {
814
+ return true;
815
+ }
816
+ return Boolean(params.requestedSchema?.properties?.optionId);
817
+ }
818
+ async handleSelectionRequest(params) {
819
+ const optionMap = (params.options || []).filter((option) => Boolean(option.optionId));
820
+ const options = optionMap.map((option) => ({
821
+ optionId: option.optionId,
822
+ label: option.label || option.name || option.optionId,
823
+ description: option.description || option.name || option.optionId
824
+ }));
825
+ const defaultOptionId = params.requestedSchema?.properties?.optionId?.default;
826
+ if (options.length === 0) {
827
+ api.logger.debug("[CodexMCP] Selection request has no options, cancelling");
828
+ return {
829
+ action: "cancel"
830
+ };
831
+ }
832
+ if (!this.selectionHandler) {
833
+ api.logger.debug("[CodexMCP] No selection handler set, cancelling selection request");
834
+ return {
835
+ action: "cancel"
836
+ };
837
+ }
838
+ const requestId = params.codex_event_id || params.codex_elicitation || `selection-${Date.now()}`;
839
+ const response = await this.selectionHandler.requestSelection({
840
+ id: requestId,
841
+ message: params.message,
842
+ options,
843
+ defaultOptionId
844
+ });
845
+ return {
846
+ action: "accept",
847
+ optionId: response.optionId,
848
+ content: {
849
+ optionId: response.optionId
850
+ }
851
+ };
852
+ }
853
+ takeExecApprovalContextForPermission(params) {
854
+ const hasInlinePayload = Boolean(
855
+ params.codex_command || params.command || params.codex_cwd || params.cwd || params.proposed_execpolicy_amendment || params.parsed_cmd
856
+ );
857
+ const explicitId = params.codex_call_id || params.call_id || params.codex_mcp_tool_call_id || params.codex_event_id;
858
+ if (hasInlinePayload && explicitId) {
859
+ return null;
860
+ }
861
+ if (!this.pendingExecApprovalContexts.length) {
862
+ return null;
863
+ }
864
+ if (explicitId) {
865
+ const index = this.pendingExecApprovalContexts.findIndex((context) => context.call_id === explicitId);
866
+ if (index >= 0) {
867
+ return this.pendingExecApprovalContexts.splice(index, 1)[0] || null;
868
+ }
869
+ }
870
+ return this.pendingExecApprovalContexts.shift() || null;
871
+ }
872
+ async startSession(config, options) {
873
+ if (!this.connected) await this.connect();
874
+ api.logger.debug("[CodexMCP] Starting Codex session:", config);
875
+ const response = await this.callToolWithReconnectRetry(
876
+ () => this.client.callTool({
877
+ name: "codex",
878
+ arguments: config
879
+ }, void 0, {
880
+ signal: options?.signal,
881
+ timeout: DEFAULT_TIMEOUT
882
+ })
883
+ );
884
+ api.logger.debug("[CodexMCP] startSession response:", response);
885
+ this.extractIdentifiers(response);
886
+ return response;
887
+ }
888
+ async continueSession(prompt, options) {
889
+ if (!this.connected) await this.connect();
890
+ if (!this.sessionId) {
891
+ throw new Error("No active session. Call startSession first.");
892
+ }
893
+ if (!this.conversationId) {
894
+ this.conversationId = this.sessionId;
895
+ api.logger.debug("[CodexMCP] conversationId missing, defaulting to sessionId:", this.conversationId);
896
+ }
897
+ const args = { sessionId: this.sessionId, conversationId: this.conversationId, prompt };
898
+ api.logger.debug("[CodexMCP] Continuing Codex session:", args);
899
+ const response = await this.callToolWithReconnectRetry(
900
+ () => this.client.callTool({
901
+ name: "codex-reply",
902
+ arguments: args
903
+ }, void 0, {
904
+ signal: options?.signal,
905
+ timeout: DEFAULT_TIMEOUT
906
+ })
907
+ );
908
+ api.logger.debug("[CodexMCP] continueSession response:", response);
909
+ this.extractIdentifiers(response);
910
+ return response;
911
+ }
912
+ updateIdentifiersFromEvent(event) {
913
+ if (!event || typeof event !== "object") {
914
+ return;
915
+ }
916
+ const candidates = [event];
917
+ if (event.data && typeof event.data === "object") {
918
+ candidates.push(event.data);
919
+ }
920
+ for (const candidate of candidates) {
921
+ const sessionId = candidate.session_id ?? candidate.sessionId ?? candidate.thread_id ?? candidate.threadId;
922
+ if (sessionId) {
923
+ this.sessionId = sessionId;
924
+ api.logger.debug("[CodexMCP] Session ID extracted from event:", this.sessionId);
925
+ }
926
+ const conversationId = candidate.conversation_id ?? candidate.conversationId;
927
+ if (conversationId) {
928
+ this.conversationId = conversationId;
929
+ api.logger.debug("[CodexMCP] Conversation ID extracted from event:", this.conversationId);
930
+ }
931
+ }
932
+ }
933
+ extractIdentifiers(response) {
934
+ const identifiers = extractCodexIdentifiers(response);
935
+ if (identifiers.sessionId) {
936
+ this.sessionId = identifiers.sessionId;
937
+ api.logger.debug("[CodexMCP] Session ID extracted:", this.sessionId);
938
+ }
939
+ if (identifiers.conversationId) {
940
+ this.conversationId = identifiers.conversationId;
941
+ api.logger.debug("[CodexMCP] Conversation ID extracted:", this.conversationId);
942
+ }
943
+ const content = response?.content;
944
+ if (Array.isArray(content)) {
945
+ for (const item of content) {
946
+ if (!this.sessionId && item?.sessionId) {
947
+ this.sessionId = item.sessionId;
948
+ api.logger.debug("[CodexMCP] Session ID extracted from content:", this.sessionId);
949
+ }
950
+ if (!this.conversationId && item && typeof item === "object" && "conversationId" in item && item.conversationId) {
951
+ this.conversationId = item.conversationId;
952
+ api.logger.debug("[CodexMCP] Conversation ID extracted from content:", this.conversationId);
953
+ }
954
+ }
955
+ }
956
+ }
957
+ getSessionId() {
958
+ return this.sessionId;
959
+ }
960
+ hasActiveSession() {
961
+ return this.sessionId !== null;
962
+ }
963
+ clearSession() {
964
+ const previousSessionId = this.sessionId;
965
+ this.sessionId = null;
966
+ this.conversationId = null;
967
+ api.logger.debug("[CodexMCP] Session cleared, previous sessionId:", previousSessionId);
968
+ }
969
+ /**
970
+ * Store the current session ID without clearing it, useful for abort handling
971
+ */
972
+ storeSessionForResume() {
973
+ api.logger.debug("[CodexMCP] Storing session for potential resume:", this.sessionId);
974
+ return this.sessionId;
975
+ }
976
+ /**
977
+ * Force close the Codex MCP transport and clear all session identifiers.
978
+ * Use this for permanent shutdown (e.g. kill/exit). Prefer `disconnect()` for
979
+ * transient connection resets where you may want to keep the session id.
980
+ */
981
+ async forceCloseSession() {
982
+ api.logger.debug("[CodexMCP] Force closing session");
983
+ try {
984
+ await this.disconnect();
985
+ } finally {
986
+ this.clearSession();
987
+ }
988
+ api.logger.debug("[CodexMCP] Session force-closed");
989
+ }
990
+ async disconnect() {
991
+ if (!this.connected) return;
992
+ const pid = this.transport?.pid ?? null;
993
+ api.logger.debug(`[CodexMCP] Disconnecting; child pid=${pid ?? "none"}`);
994
+ try {
995
+ api.logger.debug("[CodexMCP] client.close begin");
996
+ await this.client.close();
997
+ api.logger.debug("[CodexMCP] client.close done");
998
+ } catch (e) {
999
+ api.logger.debug("[CodexMCP] Error closing client, attempting transport close directly", e);
1000
+ try {
1001
+ api.logger.debug("[CodexMCP] transport.close begin");
1002
+ await this.transport?.close?.();
1003
+ api.logger.debug("[CodexMCP] transport.close done");
1004
+ } catch {
1005
+ }
1006
+ }
1007
+ if (pid) {
1008
+ try {
1009
+ process.kill(pid, 0);
1010
+ api.logger.debug("[CodexMCP] Child still alive, sending SIGKILL");
1011
+ try {
1012
+ process.kill(pid, "SIGKILL");
1013
+ } catch {
1014
+ }
1015
+ } catch {
1016
+ }
1017
+ }
1018
+ this.transport = null;
1019
+ this.connected = false;
1020
+ this.client = this.createClient();
1021
+ api.logger.debug(`[CodexMCP] Disconnected; session ${this.sessionId ?? "none"} preserved`);
1022
+ }
1023
+ }
1024
+
1025
+ class CodexPermissionHandler extends BaseReasoningProcessor.BasePermissionHandler {
1026
+ constructor(session) {
1027
+ super(session);
1028
+ }
1029
+ getLogPrefix() {
1030
+ return "[Codex]";
1031
+ }
1032
+ /**
1033
+ * Handle a tool permission request
1034
+ * @param toolCallId - The unique ID of the tool call
1035
+ * @param toolName - The name of the tool being called
1036
+ * @param input - The input parameters for the tool
1037
+ * @returns Promise resolving to permission result
1038
+ */
1039
+ async handleToolCall(toolCallId, toolName, input) {
1040
+ return new Promise((resolve, reject) => {
1041
+ this.pendingRequests.set(toolCallId, {
1042
+ resolve,
1043
+ reject,
1044
+ toolName,
1045
+ input
1046
+ });
1047
+ this.addPendingRequestToState(toolCallId, toolName, input);
1048
+ api.logger.debug(`${this.getLogPrefix()} Permission request sent for tool: ${toolName} (${toolCallId})`);
1049
+ });
1050
+ }
1051
+ }
1052
+
1053
+ class CodexSelectionHandler {
1054
+ pendingRequests = /* @__PURE__ */ new Map();
1055
+ session;
1056
+ constructor(session) {
1057
+ this.session = session;
1058
+ this.setupRpcHandler();
1059
+ }
1060
+ updateSession(newSession) {
1061
+ this.session = newSession;
1062
+ this.setupRpcHandler();
1063
+ }
1064
+ async requestSelection(request) {
1065
+ return new Promise((resolve, reject) => {
1066
+ this.pendingRequests.set(request.id, {
1067
+ resolve,
1068
+ reject,
1069
+ request
1070
+ });
1071
+ this.session.updateAgentState((currentState) => ({
1072
+ ...currentState,
1073
+ requests: {
1074
+ ...currentState.requests,
1075
+ [request.id]: {
1076
+ tool: "AskUserQuestion",
1077
+ arguments: {
1078
+ requestKind: "selection",
1079
+ questions: [
1080
+ {
1081
+ header: "Codex",
1082
+ question: request.message,
1083
+ multiSelect: false,
1084
+ options: request.options.map((option) => ({
1085
+ label: option.label,
1086
+ description: option.description || option.optionId,
1087
+ optionId: option.optionId
1088
+ }))
1089
+ }
1090
+ ]
1091
+ },
1092
+ createdAt: Date.now(),
1093
+ requestKind: "selection",
1094
+ options: request.options,
1095
+ defaultOptionId: request.defaultOptionId
1096
+ }
1097
+ }
1098
+ }));
1099
+ api.logger.debug(`[Codex] Selection request sent (${request.id}) with ${request.options.length} options`);
1100
+ });
1101
+ }
1102
+ hasPendingRequests() {
1103
+ return this.pendingRequests.size > 0;
1104
+ }
1105
+ supersedePendingRequests(reason = BaseReasoningProcessor.INTERACTION_SUPERSEDED_ERROR) {
1106
+ const pendingSnapshot = Array.from(this.pendingRequests.entries());
1107
+ if (pendingSnapshot.length === 0) {
1108
+ return 0;
1109
+ }
1110
+ this.pendingRequests.clear();
1111
+ const completedAt = Date.now();
1112
+ for (const [, pending] of pendingSnapshot) {
1113
+ pending.reject(new Error(reason));
1114
+ }
1115
+ this.session.updateAgentState((currentState) => {
1116
+ const requests = { ...currentState.requests || {} };
1117
+ const completedRequests = { ...currentState.completedRequests || {} };
1118
+ for (const [id, request] of Object.entries(requests)) {
1119
+ if (request.requestKind !== "selection") {
1120
+ continue;
1121
+ }
1122
+ completedRequests[id] = {
1123
+ ...request,
1124
+ completedAt,
1125
+ status: "canceled",
1126
+ reason,
1127
+ requestKind: "selection"
1128
+ };
1129
+ delete requests[id];
1130
+ }
1131
+ return {
1132
+ ...currentState,
1133
+ requests,
1134
+ completedRequests
1135
+ };
1136
+ });
1137
+ api.logger.debug(`[Codex] Superseded ${pendingSnapshot.length} pending selection request(s)`);
1138
+ return pendingSnapshot.length;
1139
+ }
1140
+ reset(reason = "Session reset") {
1141
+ const pendingSnapshot = Array.from(this.pendingRequests.entries());
1142
+ this.pendingRequests.clear();
1143
+ for (const [, pending] of pendingSnapshot) {
1144
+ pending.reject(new Error(reason));
1145
+ }
1146
+ this.session.updateAgentState((currentState) => {
1147
+ const requests = { ...currentState.requests || {} };
1148
+ const completedRequests = { ...currentState.completedRequests || {} };
1149
+ for (const [id, request] of Object.entries(requests)) {
1150
+ if (request.requestKind !== "selection") {
1151
+ continue;
1152
+ }
1153
+ completedRequests[id] = {
1154
+ ...request,
1155
+ completedAt: Date.now(),
1156
+ status: "canceled",
1157
+ reason,
1158
+ requestKind: "selection"
1159
+ };
1160
+ delete requests[id];
1161
+ }
1162
+ return {
1163
+ ...currentState,
1164
+ requests,
1165
+ completedRequests
1166
+ };
1167
+ });
1168
+ }
1169
+ setupRpcHandler() {
1170
+ this.session.rpcHandlerManager.registerHandler("selection", async (response) => {
1171
+ const pending = this.pendingRequests.get(response.id);
1172
+ if (!pending) {
1173
+ api.logger.debug("[Codex] Selection request not found or already resolved");
1174
+ return;
1175
+ }
1176
+ this.pendingRequests.delete(response.id);
1177
+ pending.resolve(response);
1178
+ this.session.updateAgentState((currentState) => {
1179
+ const request = currentState.requests?.[response.id];
1180
+ if (!request) {
1181
+ return currentState;
1182
+ }
1183
+ const { [response.id]: _, ...remainingRequests } = currentState.requests || {};
1184
+ return {
1185
+ ...currentState,
1186
+ requests: remainingRequests,
1187
+ completedRequests: {
1188
+ ...currentState.completedRequests,
1189
+ [response.id]: {
1190
+ ...request,
1191
+ completedAt: Date.now(),
1192
+ status: "approved",
1193
+ requestKind: "selection",
1194
+ selectedOptionId: response.optionId
1195
+ }
1196
+ }
1197
+ };
1198
+ });
1199
+ });
1200
+ }
1201
+ }
1202
+
1203
+ class ReasoningProcessor extends BaseReasoningProcessor.BaseReasoningProcessor {
1204
+ getToolName() {
1205
+ return "CodexReasoning";
1206
+ }
1207
+ getLogPrefix() {
1208
+ return "[ReasoningProcessor]";
1209
+ }
1210
+ /**
1211
+ * Process a reasoning delta and accumulate content.
1212
+ */
1213
+ processDelta(delta) {
1214
+ this.processInput(delta);
1215
+ }
1216
+ /**
1217
+ * Complete the reasoning section with final text.
1218
+ */
1219
+ complete(fullText) {
1220
+ this.completeReasoning(fullText);
1221
+ }
1222
+ }
1223
+
1224
+ class DiffProcessor {
1225
+ previousDiff = null;
1226
+ onMessage = null;
1227
+ constructor(onMessage) {
1228
+ this.onMessage = onMessage || null;
1229
+ }
1230
+ /**
1231
+ * Process a turn_diff message and check if the unified_diff has changed
1232
+ */
1233
+ processDiff(unifiedDiff) {
1234
+ if (this.previousDiff !== unifiedDiff) {
1235
+ api.logger.debug("[DiffProcessor] Unified diff changed, sending CodexDiff tool call");
1236
+ const callId = node_crypto.randomUUID();
1237
+ const toolCall = {
1238
+ type: "tool-call",
1239
+ name: "CodexDiff",
1240
+ callId,
1241
+ input: {
1242
+ unified_diff: unifiedDiff
1243
+ },
1244
+ id: node_crypto.randomUUID()
1245
+ };
1246
+ this.onMessage?.(toolCall);
1247
+ const toolResult = {
1248
+ type: "tool-call-result",
1249
+ callId,
1250
+ output: {
1251
+ status: "completed"
1252
+ },
1253
+ id: node_crypto.randomUUID()
1254
+ };
1255
+ this.onMessage?.(toolResult);
1256
+ }
1257
+ this.previousDiff = unifiedDiff;
1258
+ api.logger.debug("[DiffProcessor] Updated stored diff");
1259
+ }
1260
+ /**
1261
+ * Reset the processor state (called on task_complete or turn_aborted)
1262
+ */
1263
+ reset() {
1264
+ api.logger.debug("[DiffProcessor] Resetting diff state");
1265
+ this.previousDiff = null;
1266
+ }
1267
+ /**
1268
+ * Set the message callback for sending messages directly
1269
+ */
1270
+ setMessageCallback(callback) {
1271
+ this.onMessage = callback;
1272
+ }
1273
+ /**
1274
+ * Get the current diff value
1275
+ */
1276
+ getCurrentDiff() {
1277
+ return this.previousDiff;
1278
+ }
1279
+ }
1280
+
1281
+ function shouldEmitCodexReadyOnTurnEnd(opts) {
1282
+ return !opts.shouldExit && opts.exitReason !== "switch";
1283
+ }
1284
+ function shouldEmitCodexReadyAfterWait(opts) {
1285
+ if (opts.readyAlreadySent) {
1286
+ return false;
1287
+ }
1288
+ return !opts.shouldExit && !opts.pending && opts.queueSize === 0;
1289
+ }
1290
+ function registerCodexRemoteRpcHandlers(clientSession, handlers) {
1291
+ clientSession.rpcHandlerManager.registerHandler("abort", handlers.handleAbort);
1292
+ clientSession.rpcHandlerManager.registerHandler("switch", handlers.handleSwitchToLocal);
1293
+ registerKillSessionHandler.registerKillSessionHandler(clientSession.rpcHandlerManager, handlers.handleKillSession);
1294
+ }
1295
+ async function codexRemoteLauncher(session) {
1296
+ const messageBuffer = new registerKillSessionHandler.MessageBuffer();
1297
+ const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
1298
+ let inkInstance = null;
1299
+ let exitReason = null;
1300
+ let shouldExit = false;
1301
+ let abortController = new AbortController();
1302
+ let storedSessionIdForResume = null;
1303
+ const client = new CodexMcpClient();
1304
+ let pendingModeResumeFile = null;
1305
+ const permissionHandler = new CodexPermissionHandler(session.client);
1306
+ const selectionHandler = new CodexSelectionHandler(session.client);
1307
+ const reasoningProcessor = new ReasoningProcessor((message) => {
1308
+ session.runtimeSession.sendCodexMessage(message);
1309
+ });
1310
+ const diffProcessor = new DiffProcessor((message) => {
1311
+ session.runtimeSession.sendCodexMessage(message);
1312
+ });
1313
+ const handleSwitchToLocal = async () => {
1314
+ api.logger.debug("[Codex] Switching to local from RPC");
1315
+ exitReason = "switch";
1316
+ shouldExit = true;
1317
+ await handleAbort();
1318
+ };
1319
+ client.setPermissionHandler(permissionHandler);
1320
+ client.setSelectionHandler(selectionHandler);
1321
+ session.setPendingInteractionSuperseder((reason = BaseReasoningProcessor.INTERACTION_SUPERSEDED_ERROR) => {
1322
+ const supersededPermissions = permissionHandler.supersedePendingRequests(reason);
1323
+ const supersededSelections = selectionHandler.supersedePendingRequests(reason);
1324
+ const total = supersededPermissions + supersededSelections;
1325
+ if (total > 0) {
1326
+ const message = "Previous pending interaction was canceled because a new user message arrived. Processing the latest message instead.";
1327
+ messageBuffer.addMessage(message, "status");
1328
+ session.runtimeSession.sendSessionEvent({ type: "message", message });
1329
+ }
1330
+ return total > 0;
1331
+ });
1332
+ const handleClientSwap = (clientSession) => {
1333
+ permissionHandler.updateSession(clientSession);
1334
+ selectionHandler.updateSession(clientSession);
1335
+ registerCodexRemoteRpcHandlers(clientSession, {
1336
+ handleAbort,
1337
+ handleSwitchToLocal,
1338
+ handleKillSession
1339
+ });
1340
+ };
1341
+ session.addClientSwapCallback(handleClientSwap);
1342
+ const sendReady = () => {
1343
+ session.runtimeSession.sendSessionEvent({ type: "ready" });
1344
+ try {
1345
+ session.api.push().sendToAllDevices(
1346
+ "It's ready!",
1347
+ "Codex is waiting for your command",
1348
+ { sessionId: session.runtimeSession.sessionId }
1349
+ );
1350
+ } catch (pushError) {
1351
+ api.logger.debug("[Codex] Failed to send ready push", pushError);
1352
+ }
1353
+ };
1354
+ async function handleAbort() {
1355
+ api.logger.debug("[Codex] Abort requested - stopping current task");
1356
+ try {
1357
+ if (client.hasActiveSession()) {
1358
+ storedSessionIdForResume = client.storeSessionForResume();
1359
+ }
1360
+ abortController.abort();
1361
+ reasoningProcessor.abort();
1362
+ } catch (error) {
1363
+ api.logger.debug("[Codex] Error during abort:", error);
1364
+ } finally {
1365
+ abortController = new AbortController();
1366
+ }
1367
+ }
1368
+ async function handleKillSession() {
1369
+ api.logger.debug("[Codex] Kill session requested - terminating process");
1370
+ await handleAbort();
1371
+ try {
1372
+ session.runtimeSession.updateMetadata((currentMetadata) => ({
1373
+ ...currentMetadata,
1374
+ lifecycleState: "archived",
1375
+ lifecycleStateSince: Date.now(),
1376
+ archivedBy: "cli",
1377
+ archiveReason: "User terminated"
1378
+ }));
1379
+ session.runtimeSession.sendSessionDeath();
1380
+ await session.runtimeSession.flush();
1381
+ await session.runtimeSession.close();
1382
+ await client.forceCloseSession();
1383
+ index.stopCaffeinate();
1384
+ process.exit(0);
1385
+ } catch (error) {
1386
+ api.logger.debug("[Codex] Error during session termination:", error);
1387
+ process.exit(1);
1388
+ }
1389
+ }
1390
+ if (hasTTY) {
1391
+ console.clear();
1392
+ inkInstance = ink.render(React.createElement(CodexDisplay, {
1393
+ messageBuffer,
1394
+ logPath: process.env.DEBUG ? session.logPath : void 0,
1395
+ title: "Remote Mode - Codex Messages",
1396
+ onExit: async () => {
1397
+ api.logger.debug("[Codex] Switching to exit from local keyboard");
1398
+ exitReason = "exit";
1399
+ shouldExit = true;
1400
+ await handleAbort();
1401
+ },
1402
+ onSwitchToLocal: async () => {
1403
+ api.logger.debug("[Codex] Switching to local from local keyboard");
1404
+ exitReason = "switch";
1405
+ shouldExit = true;
1406
+ await handleAbort();
1407
+ }
1408
+ }), {
1409
+ exitOnCtrlC: false,
1410
+ patchConsole: false
1411
+ });
1412
+ }
1413
+ if (hasTTY) {
1414
+ process.stdin.resume();
1415
+ if (process.stdin.isTTY) {
1416
+ process.stdin.setRawMode(true);
1417
+ }
1418
+ process.stdin.setEncoding("utf8");
1419
+ }
1420
+ registerCodexRemoteRpcHandlers(session.client, {
1421
+ handleAbort,
1422
+ handleSwitchToLocal,
1423
+ handleKillSession
1424
+ });
1425
+ client.setHandler((msg) => {
1426
+ api.logger.debug(`[Codex] Message: ${JSON.stringify(msg)}`);
1427
+ if (msg.type === "agent_message") {
1428
+ messageBuffer.addMessage(msg.message, "assistant");
1429
+ } else if (msg.type === "agent_reasoning") {
1430
+ messageBuffer.addMessage(`[Thinking] ${msg.text.substring(0, 100)}...`, "system");
1431
+ } else if (msg.type === "exec_command_begin") {
1432
+ messageBuffer.addMessage(`Executing: ${msg.command}`, "tool");
1433
+ } else if (msg.type === "exec_command_end") {
1434
+ const output = msg.output || msg.error || "Command completed";
1435
+ const truncatedOutput = output.substring(0, 200);
1436
+ messageBuffer.addMessage(`Result: ${truncatedOutput}${output.length > 200 ? "..." : ""}`, "result");
1437
+ } else if (msg.type === "task_started") {
1438
+ messageBuffer.addMessage("Starting task...", "status");
1439
+ session.onThinkingChange(true);
1440
+ } else if (msg.type === "task_complete") {
1441
+ messageBuffer.addMessage("Task completed", "status");
1442
+ session.onThinkingChange(false);
1443
+ diffProcessor.reset();
1444
+ if (shouldEmitCodexReadyOnTurnEnd({ shouldExit, exitReason })) {
1445
+ sendReady();
1446
+ readyAlreadySent = true;
1447
+ }
1448
+ } else if (msg.type === "turn_aborted") {
1449
+ messageBuffer.addMessage("Turn aborted", "status");
1450
+ session.onThinkingChange(false);
1451
+ diffProcessor.reset();
1452
+ if (shouldEmitCodexReadyOnTurnEnd({ shouldExit, exitReason })) {
1453
+ sendReady();
1454
+ readyAlreadySent = true;
1455
+ }
1456
+ }
1457
+ if (msg.type === "agent_reasoning_section_break") {
1458
+ reasoningProcessor.handleSectionBreak();
1459
+ }
1460
+ if (msg.type === "agent_reasoning_delta") {
1461
+ reasoningProcessor.processDelta(msg.delta);
1462
+ }
1463
+ if (msg.type === "agent_reasoning") {
1464
+ reasoningProcessor.complete(msg.text);
1465
+ }
1466
+ if (msg.type === "agent_message") {
1467
+ session.runtimeSession.sendCodexMessage({
1468
+ type: "message",
1469
+ message: msg.message,
1470
+ id: node_crypto.randomUUID()
1471
+ });
1472
+ }
1473
+ if (msg.type === "exec_command_begin" || msg.type === "exec_approval_request") {
1474
+ const { call_id, type, ...inputs } = msg;
1475
+ session.runtimeSession.sendCodexMessage({
1476
+ type: "tool-call",
1477
+ name: "CodexBash",
1478
+ callId: call_id,
1479
+ input: inputs,
1480
+ id: node_crypto.randomUUID()
1481
+ });
1482
+ }
1483
+ if (msg.type === "exec_command_end") {
1484
+ const { call_id, type, ...output } = msg;
1485
+ session.runtimeSession.sendCodexMessage({
1486
+ type: "tool-call-result",
1487
+ callId: call_id,
1488
+ output,
1489
+ id: node_crypto.randomUUID()
1490
+ });
1491
+ }
1492
+ if (msg.type === "token_count") {
1493
+ session.runtimeSession.sendCodexMessage({
1494
+ ...msg,
1495
+ id: node_crypto.randomUUID()
1496
+ });
1497
+ }
1498
+ if (msg.type === "patch_apply_begin") {
1499
+ const { call_id, auto_approved, changes } = msg;
1500
+ const changeCount = Object.keys(changes).length;
1501
+ const filesMsg = changeCount === 1 ? "1 file" : `${changeCount} files`;
1502
+ messageBuffer.addMessage(`Modifying ${filesMsg}...`, "tool");
1503
+ session.runtimeSession.sendCodexMessage({
1504
+ type: "tool-call",
1505
+ name: "CodexPatch",
1506
+ callId: call_id,
1507
+ input: { auto_approved, changes },
1508
+ id: node_crypto.randomUUID()
1509
+ });
1510
+ }
1511
+ if (msg.type === "patch_apply_end") {
1512
+ const { call_id, stdout, stderr, success } = msg;
1513
+ if (success) {
1514
+ messageBuffer.addMessage((stdout || "Files modified successfully").substring(0, 200), "result");
1515
+ } else {
1516
+ messageBuffer.addMessage(`Error: ${(stderr || "Failed to modify files").substring(0, 200)}`, "result");
1517
+ }
1518
+ session.runtimeSession.sendCodexMessage({
1519
+ type: "tool-call-result",
1520
+ callId: call_id,
1521
+ output: { stdout, stderr, success },
1522
+ id: node_crypto.randomUUID()
1523
+ });
1524
+ }
1525
+ if (msg.type === "turn_diff" && msg.unified_diff) {
1526
+ diffProcessor.processDiff(msg.unified_diff);
1527
+ }
1528
+ });
1529
+ let readyAlreadySent = false;
1530
+ try {
1531
+ await client.connect();
1532
+ let wasCreated = false;
1533
+ let currentModeHash = null;
1534
+ let pending = null;
1535
+ while (!shouldExit) {
1536
+ let message = pending;
1537
+ pending = null;
1538
+ if (!message) {
1539
+ const waitSignal = abortController.signal;
1540
+ const batch = await session.queue.waitForMessagesAndGetAsString(waitSignal);
1541
+ if (!batch) {
1542
+ if (waitSignal.aborted && !shouldExit) {
1543
+ continue;
1544
+ }
1545
+ break;
1546
+ }
1547
+ message = batch;
1548
+ }
1549
+ if (!message) {
1550
+ break;
1551
+ }
1552
+ if (wasCreated && currentModeHash && message.hash !== currentModeHash) {
1553
+ messageBuffer.addMessage("\u2550".repeat(40), "status");
1554
+ messageBuffer.addMessage("Starting new Codex session (mode changed)...", "status");
1555
+ const prevSessionId = client.getSessionId();
1556
+ pendingModeResumeFile = findCodexResumeFile(prevSessionId);
1557
+ client.clearSession();
1558
+ session.clearSessionId();
1559
+ wasCreated = false;
1560
+ currentModeHash = null;
1561
+ pending = message;
1562
+ permissionHandler.reset();
1563
+ selectionHandler.reset();
1564
+ reasoningProcessor.abort();
1565
+ diffProcessor.reset();
1566
+ session.onThinkingChange(false);
1567
+ continue;
1568
+ }
1569
+ currentModeHash = message.hash;
1570
+ readyAlreadySent = false;
1571
+ messageBuffer.addMessage(message.message, "user");
1572
+ try {
1573
+ const approvalPolicy = (() => {
1574
+ switch (message.mode.permissionMode) {
1575
+ case "default":
1576
+ return "untrusted";
1577
+ case "read-only":
1578
+ return "never";
1579
+ case "safe-yolo":
1580
+ return "on-failure";
1581
+ case "yolo":
1582
+ return "on-failure";
1583
+ case "bypassPermissions":
1584
+ return "on-failure";
1585
+ case "acceptEdits":
1586
+ return "on-request";
1587
+ case "plan":
1588
+ return "untrusted";
1589
+ default:
1590
+ return "untrusted";
1591
+ }
1592
+ })();
1593
+ const sandbox = (() => {
1594
+ switch (message.mode.permissionMode) {
1595
+ case "default":
1596
+ return "workspace-write";
1597
+ case "read-only":
1598
+ return "read-only";
1599
+ case "safe-yolo":
1600
+ return "workspace-write";
1601
+ case "yolo":
1602
+ return "danger-full-access";
1603
+ case "bypassPermissions":
1604
+ return "danger-full-access";
1605
+ case "acceptEdits":
1606
+ return "workspace-write";
1607
+ case "plan":
1608
+ return "workspace-write";
1609
+ default:
1610
+ return "workspace-write";
1611
+ }
1612
+ })();
1613
+ if (!wasCreated) {
1614
+ const startConfig = {
1615
+ prompt: message.message,
1616
+ sandbox,
1617
+ "approval-policy": approvalPolicy,
1618
+ config: {}
1619
+ };
1620
+ if (message.mode.model) {
1621
+ startConfig.model = message.mode.model;
1622
+ }
1623
+ let resumeFile = null;
1624
+ if (pendingModeResumeFile) {
1625
+ resumeFile = pendingModeResumeFile;
1626
+ pendingModeResumeFile = null;
1627
+ } else if (storedSessionIdForResume) {
1628
+ resumeFile = findCodexResumeFile(storedSessionIdForResume);
1629
+ storedSessionIdForResume = null;
1630
+ } else if (session.sessionId) {
1631
+ resumeFile = findCodexResumeFile(session.sessionId);
1632
+ }
1633
+ if (resumeFile) {
1634
+ startConfig.config = {
1635
+ ...startConfig.config || {},
1636
+ experimental_resume: resumeFile
1637
+ };
1638
+ }
1639
+ await client.startSession(startConfig, { signal: abortController.signal });
1640
+ const currentSessionId = client.getSessionId();
1641
+ if (currentSessionId) {
1642
+ session.onSessionFound(currentSessionId);
1643
+ }
1644
+ wasCreated = true;
1645
+ } else {
1646
+ await client.continueSession(message.message, { signal: abortController.signal });
1647
+ }
1648
+ } catch (error) {
1649
+ api.logger.warn("Error in codex session:", error);
1650
+ const isAbortError = error instanceof Error && error.name === "AbortError";
1651
+ const isExpectedInterruption = isAbortError || abortController.signal.aborted || shouldExit || exitReason === "switch";
1652
+ if (isExpectedInterruption) {
1653
+ messageBuffer.addMessage("Aborted by user", "status");
1654
+ session.runtimeSession.sendSessionEvent({ type: "message", message: "Aborted by user" });
1655
+ } else {
1656
+ messageBuffer.addMessage("Process exited unexpectedly", "status");
1657
+ session.runtimeSession.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
1658
+ if (client.hasActiveSession()) {
1659
+ storedSessionIdForResume = client.storeSessionForResume();
1660
+ }
1661
+ }
1662
+ } finally {
1663
+ permissionHandler.reset();
1664
+ selectionHandler.reset();
1665
+ reasoningProcessor.abort();
1666
+ diffProcessor.reset();
1667
+ session.onThinkingChange(false);
1668
+ if (shouldEmitCodexReadyAfterWait({
1669
+ pending: Boolean(pending),
1670
+ queueSize: session.queue.size(),
1671
+ shouldExit,
1672
+ readyAlreadySent
1673
+ })) {
1674
+ sendReady();
1675
+ readyAlreadySent = true;
1676
+ }
1677
+ }
1678
+ }
1679
+ } finally {
1680
+ client.setHandler(null);
1681
+ client.setPermissionHandler(null);
1682
+ client.setSelectionHandler(null);
1683
+ if (exitReason === "switch") {
1684
+ api.logger.debug("[Codex] Remote launcher exiting via switch; waiting for MCP transport disconnect");
1685
+ await client.disconnect();
1686
+ } else {
1687
+ await client.forceCloseSession();
1688
+ }
1689
+ permissionHandler.reset();
1690
+ selectionHandler.reset();
1691
+ session.setPendingInteractionSuperseder(null);
1692
+ session.removeClientSwapCallback(handleClientSwap);
1693
+ if (process.stdin.isTTY) {
1694
+ try {
1695
+ process.stdin.setRawMode(false);
1696
+ } catch {
1697
+ }
1698
+ }
1699
+ if (hasTTY) {
1700
+ try {
1701
+ process.stdin.pause();
1702
+ } catch {
1703
+ }
1704
+ }
1705
+ session.client.rpcHandlerManager.registerHandler("abort", async () => {
1706
+ });
1707
+ session.client.rpcHandlerManager.registerHandler("switch", async () => {
1708
+ });
1709
+ if (inkInstance) {
1710
+ inkInstance.unmount();
1711
+ }
1712
+ messageBuffer.clear();
1713
+ }
1714
+ api.logger.debug(`[Codex] Remote launcher returning: ${exitReason || "exit"}`);
1715
+ return exitReason || "exit";
1716
+ }
1717
+
1718
+ async function codexLoop(opts) {
1719
+ let mode = opts.startingMode ?? "local";
1720
+ opts.session.onModeChange(mode);
1721
+ while (true) {
1722
+ api.logger.debug(`[codex-loop] Iteration with mode: ${mode}`);
1723
+ if (mode === "local") {
1724
+ const result = await codexLocalLauncher(opts.session);
1725
+ if (result.type === "switch") {
1726
+ mode = "remote";
1727
+ opts.session.onModeChange(mode);
1728
+ continue;
1729
+ }
1730
+ return result.code;
1731
+ }
1732
+ const reason = await codexRemoteLauncher(opts.session);
1733
+ if (reason === "switch") {
1734
+ mode = "local";
1735
+ opts.session.onModeChange(mode);
1736
+ continue;
1737
+ }
1738
+ return 0;
1739
+ }
1740
+ }
1741
+
1742
+ async function runCodex(opts) {
1743
+ const sessionTag = node_crypto.randomUUID();
1744
+ api.connectionState.setBackend("Codex");
1745
+ if (opts.startedBy === "daemon" && opts.startingMode === "local") {
1746
+ throw new Error("Daemon-spawned Codex sessions cannot use local mode.");
1747
+ }
1748
+ const api$1 = await api.ApiClient.create(opts.credentials);
1749
+ api.logger.debug(`[codex] Starting with options: startedBy=${opts.startedBy || "terminal"}, startingMode=${opts.startingMode || "local"}`);
1750
+ const settings = await persistence.readSettings();
1751
+ const machineId = settings?.machineId;
1752
+ if (!machineId) {
1753
+ console.error("[START] No machine ID found in settings.");
1754
+ process.exit(1);
1755
+ }
1756
+ await api$1.getOrCreateMachine({
1757
+ machineId,
1758
+ metadata: index.initialMachineMetadata
1759
+ });
1760
+ const { state, metadata } = BaseReasoningProcessor.createSessionMetadata({
1761
+ flavor: "codex",
1762
+ machineId,
1763
+ startedBy: opts.startedBy
1764
+ });
1765
+ const response = await api$1.getOrCreateSession({ tag: sessionTag, metadata, state });
1766
+ let sessionClient;
1767
+ let codexSession = null;
1768
+ const { session: initialSession, reconnectionHandle } = BaseReasoningProcessor.setupOfflineReconnection({
1769
+ api: api$1,
1770
+ sessionTag,
1771
+ metadata,
1772
+ state,
1773
+ response,
1774
+ onSessionSwap: (newSession) => {
1775
+ sessionClient = newSession;
1776
+ codexSession?.updateClient(newSession);
1777
+ }
1778
+ });
1779
+ sessionClient = initialSession;
1780
+ if (response) {
1781
+ try {
1782
+ const result = await index.notifyDaemonSessionStarted(response.id, metadata);
1783
+ if (result.error) {
1784
+ api.logger.debug("[START] Failed to report to daemon:", result.error);
1785
+ }
1786
+ } catch (error) {
1787
+ api.logger.debug("[START] Failed to report to daemon:", error);
1788
+ }
1789
+ }
1790
+ const messageQueue = new registerKillSessionHandler.MessageQueue2((mode) => registerKillSessionHandler.hashObject({
1791
+ permissionMode: mode.permissionMode,
1792
+ model: mode.model
1793
+ }));
1794
+ let currentPermissionMode;
1795
+ let currentModel;
1796
+ sessionClient.onUserMessage((message) => {
1797
+ let messagePermissionMode = currentPermissionMode;
1798
+ if (message.meta?.permissionMode) {
1799
+ messagePermissionMode = message.meta.permissionMode;
1800
+ currentPermissionMode = messagePermissionMode;
1801
+ }
1802
+ let messageModel = currentModel;
1803
+ if (message.meta?.hasOwnProperty("model")) {
1804
+ messageModel = message.meta.model || void 0;
1805
+ currentModel = messageModel;
1806
+ }
1807
+ if (opts.startingMode === "remote") {
1808
+ codexSession?.supersedePendingInteractions(BaseReasoningProcessor.INTERACTION_SUPERSEDED_ERROR);
1809
+ }
1810
+ messageQueue.push(message.content.text, {
1811
+ permissionMode: messagePermissionMode || "default",
1812
+ model: messageModel
1813
+ });
1814
+ });
1815
+ codexSession = new CodexSession({
1816
+ api: api$1,
1817
+ client: sessionClient,
1818
+ path: process.cwd(),
1819
+ logPath: api.logger.logFilePath,
1820
+ sessionId: null,
1821
+ mode: opts.startingMode ?? "local",
1822
+ messageQueue,
1823
+ codexArgs: opts.codexArgs,
1824
+ onModeChange: (mode) => {
1825
+ sessionClient.sendSessionEvent({ type: "switch", mode });
1826
+ sessionClient.updateAgentState((currentState) => ({
1827
+ ...currentState,
1828
+ controlledByUser: mode === "local"
1829
+ }));
1830
+ }
1831
+ });
1832
+ try {
1833
+ const exitCode = await codexLoop({
1834
+ session: codexSession,
1835
+ startingMode: opts.startingMode ?? "local"
1836
+ });
1837
+ sessionClient.sendSessionDeath();
1838
+ await sessionClient.flush();
1839
+ await sessionClient.close();
1840
+ index.stopCaffeinate();
1841
+ process.exit(exitCode);
1842
+ } finally {
1843
+ reconnectionHandle?.cancel();
1844
+ codexSession.cleanup();
1845
+ }
1846
+ }
1847
+
1848
+ exports.runCodex = runCodex;