happy-imou-cloud 2.0.2 → 2.0.3

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 (25) hide show
  1. package/dist/{BaseReasoningProcessor-D8VhEbs2.mjs → BaseReasoningProcessor-B37yOHxo.mjs} +2 -2
  2. package/dist/{BaseReasoningProcessor-B6tJ_eL5.cjs → BaseReasoningProcessor-_wxlqKB8.cjs} +4 -4
  3. package/dist/{api-D2Njw9Im.cjs → api-D9dIR956.cjs} +43 -43
  4. package/dist/{api-MYhAGPLn.mjs → api-DpQIC-DJ.mjs} +2 -2
  5. package/dist/{command-CVldr51S.cjs → command-CdXv1zNF.cjs} +3 -3
  6. package/dist/{command-nmK6O-ab.mjs → command-DRqrBuHM.mjs} +3 -3
  7. package/dist/{index-B97L7qLD.mjs → index-CriPm_z9.mjs} +8 -8
  8. package/dist/{index-Bg-YziG2.cjs → index-LYPXVO_L.cjs} +95 -95
  9. package/dist/index.cjs +3 -3
  10. package/dist/index.mjs +3 -3
  11. package/dist/lib.cjs +1 -1
  12. package/dist/lib.mjs +1 -1
  13. package/dist/{persistence-Dkm7rm8k.mjs → persistence-CqgPgbzN.mjs} +1 -1
  14. package/dist/{persistence-D_2GkJAO.cjs → persistence-PzKU0QCa.cjs} +28 -28
  15. package/dist/{registerKillSessionHandler-BAXmJQRt.cjs → registerKillSessionHandler-BDBPoQSA.cjs} +2 -2
  16. package/dist/{registerKillSessionHandler-5GbrO0FM.mjs → registerKillSessionHandler-C3M_-4Zg.mjs} +2 -2
  17. package/dist/{runClaude-Cii3R2Fv.mjs → runClaude-D6Pdkevn.mjs} +25 -5
  18. package/dist/{runClaude-B-GNEkKg.cjs → runClaude-IeRSC5qX.cjs} +53 -33
  19. package/dist/{runCodex-C--ZwAhl.mjs → runCodex-CsfUU1Wb.mjs} +216 -401
  20. package/dist/{runCodex-CPHyGwj9.cjs → runCodex-WRmgSK6L.cjs} +216 -401
  21. package/dist/{runGemini-CQp7Nuzn.mjs → runGemini-CrH3dQ0Y.mjs} +5 -5
  22. package/dist/{runGemini-DaDz1bzQ.cjs → runGemini-qBh6zs5G.cjs} +5 -5
  23. package/package.json +1 -1
  24. package/dist/future-Dq4Ha1Dn.cjs +0 -24
  25. package/dist/future-xRdLl3vf.mjs +0 -22
@@ -1,19 +1,17 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { l as logger, b as connectionState, A as ApiClient, i as isAuthenticationRequiredError } from './api-MYhAGPLn.mjs';
3
- import { readSettings } from './persistence-Dkm7rm8k.mjs';
4
- import { f as formatDisplayMessage, v as validateCodexAcpSpawn, d as createCodexBackend, t as truncateDisplayMessage, b as stopCaffeinate, i as initialMachineMetadata, n as notifyDaemonSessionStarted } from './index-B97L7qLD.mjs';
5
- import { B as BasePermissionHandler, g as getPendingInteractionTimeoutMs, I as INTERACTION_SUPERSEDED_ERROR, b as INTERACTION_TIMED_OUT_ERROR, a as BaseReasoningProcessor, c as createSessionMetadata, s as setupOfflineReconnection } from './BaseReasoningProcessor-D8VhEbs2.mjs';
6
- import { a as MessageBuffer, r as registerKillSessionHandler, M as MessageQueue2, h as hashObject } from './registerKillSessionHandler-5GbrO0FM.mjs';
7
- import { F as Future } from './future-xRdLl3vf.mjs';
8
- import { spawn, execFileSync } from 'node:child_process';
9
- import fs from 'node:fs';
10
- import os from 'node:os';
11
- import path, { join } from 'node:path';
2
+ import { l as logger, b as connectionState, A as ApiClient, i as isAuthenticationRequiredError } from './api-DpQIC-DJ.mjs';
3
+ import { readSettings } from './persistence-CqgPgbzN.mjs';
4
+ import { f as formatDisplayMessage, v as validateCodexAcpSpawn, d as createCodexBackend, t as truncateDisplayMessage, b as stopCaffeinate, i as initialMachineMetadata, n as notifyDaemonSessionStarted } from './index-CriPm_z9.mjs';
5
+ import { B as BasePermissionHandler, g as getPendingInteractionTimeoutMs, I as INTERACTION_SUPERSEDED_ERROR, b as INTERACTION_TIMED_OUT_ERROR, a as BaseReasoningProcessor, c as createSessionMetadata, s as setupOfflineReconnection } from './BaseReasoningProcessor-B37yOHxo.mjs';
6
+ import { h as hashObject, a as MessageBuffer, r as registerKillSessionHandler, M as MessageQueue2 } from './registerKillSessionHandler-C3M_-4Zg.mjs';
12
7
  import React, { useState, useRef, useEffect, useCallback } from 'react';
13
8
  import { useStdout, useInput, Box, Text, render } from 'ink';
14
9
  import 'axios';
15
10
  import 'chalk';
16
11
  import 'fs';
12
+ import 'node:fs';
13
+ import 'node:os';
14
+ import 'node:path';
17
15
  import 'node:events';
18
16
  import 'socket.io-client';
19
17
  import 'zod';
@@ -31,6 +29,7 @@ import 'qrcode-terminal';
31
29
  import 'node:module';
32
30
  import 'open';
33
31
  import 'url';
32
+ import 'node:child_process';
34
33
  import 'ps-list';
35
34
  import 'cross-spawn';
36
35
  import 'fastify';
@@ -128,7 +127,6 @@ class CodexSession {
128
127
  sessionId;
129
128
  mode;
130
129
  thinking = false;
131
- localModePinned = false;
132
130
  keepAliveInterval;
133
131
  onModeChangeCallback;
134
132
  clientSwapCallbacks = [];
@@ -188,18 +186,6 @@ class CodexSession {
188
186
  this.client.keepAlive(this.thinking, mode);
189
187
  await this.onModeChangeCallback(mode);
190
188
  };
191
- pinLocalMode = () => {
192
- this.localModePinned = true;
193
- logger.debug("[CodexSession] Local mode pinned");
194
- };
195
- clearLocalModePin = () => {
196
- if (!this.localModePinned) {
197
- return;
198
- }
199
- this.localModePinned = false;
200
- logger.debug("[CodexSession] Local mode pin cleared");
201
- };
202
- isLocalModePinned = () => this.localModePinned;
203
189
  onSessionFound = (sessionId) => {
204
190
  if (this.sessionId === sessionId) {
205
191
  return;
@@ -217,262 +203,7 @@ class CodexSession {
217
203
  };
218
204
  }
219
205
 
220
- function getCodexSessionsRoot() {
221
- const codexHomeDir = process.env.CODEX_HOME || join(os.homedir(), ".codex");
222
- return join(codexHomeDir, "sessions");
223
- }
224
- function collectFilesRecursive(dir, acc = []) {
225
- let entries;
226
- try {
227
- entries = fs.readdirSync(dir, { withFileTypes: true });
228
- } catch {
229
- return acc;
230
- }
231
- for (const entry of entries) {
232
- const full = join(dir, entry.name);
233
- if (entry.isDirectory()) {
234
- collectFilesRecursive(full, acc);
235
- } else if (entry.isFile()) {
236
- acc.push(full);
237
- }
238
- }
239
- return acc;
240
- }
241
- function extractCodexSessionIdFromPath(filePath) {
242
- 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$/);
243
- return match?.[1] ?? null;
244
- }
245
- function findLatestCodexSessionIdSince(startTimeMs) {
246
- try {
247
- const candidates = collectFilesRecursive(getCodexSessionsRoot()).filter((full) => full.endsWith(".jsonl")).filter((full) => {
248
- try {
249
- return fs.statSync(full).mtimeMs >= startTimeMs;
250
- } catch {
251
- return false;
252
- }
253
- }).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
254
- for (const candidate of candidates) {
255
- const sessionId = extractCodexSessionIdFromPath(candidate);
256
- if (sessionId) {
257
- return sessionId;
258
- }
259
- }
260
- } catch {
261
- return null;
262
- }
263
- return null;
264
- }
265
-
266
- function firstExistingPath(candidates) {
267
- for (const candidate of candidates) {
268
- try {
269
- if (fs.existsSync(candidate)) {
270
- return candidate;
271
- }
272
- } catch {
273
- }
274
- }
275
- return null;
276
- }
277
- function resolveCodexExecutable() {
278
- if (process.platform === "win32") {
279
- const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
280
- const npmGlobalBin = path.join(appData, "npm");
281
- const resolved = firstExistingPath([
282
- path.join(npmGlobalBin, "codex.cmd"),
283
- path.join(npmGlobalBin, "codex.ps1"),
284
- path.join(npmGlobalBin, "codex")
285
- ]);
286
- if (resolved) {
287
- return resolved;
288
- }
289
- }
290
- return "codex";
291
- }
292
- function shouldUseShellForCodex(executable) {
293
- return process.platform === "win32" && /\.(cmd|bat|ps1)$/i.test(executable);
294
- }
295
-
296
- class CodexExitCodeError extends Error {
297
- exitCode;
298
- constructor(exitCode) {
299
- super(`Codex process exited with code: ${exitCode}`);
300
- this.name = "CodexExitCodeError";
301
- this.exitCode = exitCode;
302
- }
303
- }
304
- function terminateCodexLocalChild(pid) {
305
- if (!pid) {
306
- return;
307
- }
308
- try {
309
- if (process.platform === "win32") {
310
- execFileSync("taskkill", ["/F", "/T", "/PID", pid.toString()], { stdio: "ignore" });
311
- return;
312
- }
313
- process.kill(pid, "SIGTERM");
314
- } catch (error) {
315
- logger.debug("[CodexLocal] Failed to terminate child process", error);
316
- }
317
- }
318
- async function codexLocal(opts) {
319
- const baseArgs = [...opts.codexArgs ?? []];
320
- const args = opts.sessionId ? ["resume", opts.sessionId, ...baseArgs] : baseArgs;
321
- const startTime = Date.now();
322
- let detectedSessionId = opts.sessionId;
323
- const codexExecutable = resolveCodexExecutable();
324
- logger.debug(`[CodexLocal] Spawning ${codexExecutable} with args: ${JSON.stringify(args)}`);
325
- process.stdin.pause();
326
- try {
327
- await new Promise((resolve, reject) => {
328
- const child = spawn(codexExecutable, args, {
329
- cwd: opts.path,
330
- env: process.env,
331
- stdio: "inherit",
332
- signal: opts.abort,
333
- shell: shouldUseShellForCodex(codexExecutable)
334
- });
335
- const abortChild = () => {
336
- if (child.exitCode === null) {
337
- logger.debug(`[CodexLocal] Abort received, terminating child tree pid=${child.pid ?? "none"}`);
338
- terminateCodexLocalChild(child.pid);
339
- }
340
- };
341
- opts.abort.addEventListener("abort", abortChild, { once: true });
342
- const sessionPoll = setInterval(() => {
343
- if (detectedSessionId) {
344
- return;
345
- }
346
- const nextSessionId = findLatestCodexSessionIdSince(startTime);
347
- if (nextSessionId) {
348
- detectedSessionId = nextSessionId;
349
- logger.debug(`[CodexLocal] Detected session ID: ${nextSessionId}`);
350
- opts.onSessionFound(nextSessionId);
351
- }
352
- }, 1e3);
353
- child.on("error", (error) => {
354
- clearInterval(sessionPoll);
355
- opts.abort.removeEventListener("abort", abortChild);
356
- reject(error);
357
- });
358
- child.on("exit", (code, signal) => {
359
- clearInterval(sessionPoll);
360
- opts.abort.removeEventListener("abort", abortChild);
361
- opts.onThinkingChange?.(false);
362
- if (signal === "SIGTERM" && opts.abort.aborted) {
363
- resolve();
364
- return;
365
- }
366
- if (signal) {
367
- reject(new Error(`Codex terminated with signal: ${signal}`));
368
- return;
369
- }
370
- if (code !== 0 && code !== null) {
371
- reject(new CodexExitCodeError(code));
372
- return;
373
- }
374
- resolve();
375
- });
376
- });
377
- } finally {
378
- process.stdin.resume();
379
- opts.onThinkingChange?.(false);
380
- }
381
- return detectedSessionId;
382
- }
383
-
384
- async function codexLocalLauncher(session) {
385
- let exitReason = null;
386
- const processAbortController = new AbortController();
387
- const exitFuture = new Future();
388
- try {
389
- async function abort() {
390
- if (!processAbortController.signal.aborted) {
391
- processAbortController.abort();
392
- }
393
- await exitFuture.promise;
394
- }
395
- async function doSwitch() {
396
- logger.debug("[codex-local]: switching to remote mode");
397
- if (!exitReason) {
398
- exitReason = { type: "switch" };
399
- }
400
- await abort();
401
- }
402
- async function doAbort() {
403
- logger.debug("[codex-local]: abort requested");
404
- if (!exitReason) {
405
- exitReason = { type: "switch" };
406
- }
407
- session.queue.reset();
408
- await abort();
409
- }
410
- session.client.rpcHandlerManager.registerHandler("abort", doAbort);
411
- session.client.rpcHandlerManager.registerHandler("switch", doSwitch);
412
- session.queue.setOnMessage(() => {
413
- if (session.isLocalModePinned()) {
414
- logger.debug("[codex-local]: message arrived while local mode is pinned; staying local");
415
- return;
416
- }
417
- void doSwitch();
418
- });
419
- if (session.queue.size() > 0) {
420
- if (session.isLocalModePinned()) {
421
- logger.debug("[codex-local]: pending queued messages detected, but local mode is pinned; staying local");
422
- } else {
423
- return { type: "switch" };
424
- }
425
- }
426
- while (true) {
427
- if (exitReason) {
428
- return exitReason;
429
- }
430
- try {
431
- const sessionId = await codexLocal({
432
- path: session.path,
433
- sessionId: session.sessionId,
434
- abort: processAbortController.signal,
435
- codexArgs: session.codexArgs,
436
- onSessionFound: session.onSessionFound,
437
- onThinkingChange: session.onThinkingChange
438
- });
439
- if (sessionId) {
440
- session.onSessionFound(sessionId);
441
- }
442
- if (!exitReason) {
443
- exitReason = { type: "exit", code: 0 };
444
- break;
445
- }
446
- } catch (error) {
447
- logger.debug("[codex-local]: launch error", error);
448
- if (error instanceof CodexExitCodeError && !exitReason) {
449
- exitReason = { type: "exit", code: error.exitCode };
450
- break;
451
- }
452
- if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "EINVAL") && !exitReason) {
453
- exitReason = { type: "exit", code: 1 };
454
- session.runtimeSession.sendSessionEvent({ type: "message", message: "Codex CLI not found. Check your local Codex installation." });
455
- break;
456
- }
457
- if (!exitReason) {
458
- session.runtimeSession.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
459
- continue;
460
- }
461
- break;
462
- }
463
- }
464
- } finally {
465
- exitFuture.resolve(void 0);
466
- session.client.rpcHandlerManager.registerHandler("abort", async () => {
467
- });
468
- session.client.rpcHandlerManager.registerHandler("switch", async () => {
469
- });
470
- session.queue.setOnMessage(null);
471
- }
472
- return exitReason || { type: "exit", code: 0 };
473
- }
474
-
475
- const CodexDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal, title }) => {
206
+ const CodexDisplay = ({ messageBuffer, logPath, onExit, title }) => {
476
207
  const [messages, setMessages] = useState([]);
477
208
  const [confirmationMode, setConfirmationMode] = useState(null);
478
209
  const [actionInProgress, setActionInProgress] = useState(null);
@@ -521,21 +252,10 @@ const CodexDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal, title }
521
252
  }
522
253
  return;
523
254
  }
524
- if (input === " ") {
525
- if (confirmationMode === "switch") {
526
- resetConfirmation();
527
- setActionInProgress("switching");
528
- await new Promise((resolve) => setTimeout(resolve, 100));
529
- onSwitchToLocal?.();
530
- } else {
531
- setConfirmationWithTimeout("switch");
532
- }
533
- return;
534
- }
535
255
  if (confirmationMode) {
536
256
  resetConfirmation();
537
257
  }
538
- }, [confirmationMode, actionInProgress, onExit, onSwitchToLocal, setConfirmationWithTimeout, resetConfirmation]));
258
+ }, [confirmationMode, actionInProgress, onExit, setConfirmationWithTimeout, resetConfirmation]));
539
259
  const getMessageColor = (type) => {
540
260
  switch (type) {
541
261
  case "user":
@@ -587,13 +307,13 @@ const CodexDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal, title }
587
307
  {
588
308
  width: terminalWidth,
589
309
  borderStyle: "round",
590
- borderColor: actionInProgress ? "gray" : confirmationMode === "exit" ? "red" : confirmationMode === "switch" ? "yellow" : "green",
310
+ borderColor: actionInProgress ? "gray" : confirmationMode === "exit" ? "red" : "green",
591
311
  paddingX: 2,
592
312
  justifyContent: "center",
593
313
  alignItems: "center",
594
314
  flexDirection: "column"
595
315
  },
596
- /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", alignItems: "center" }, actionInProgress === "exiting" ? /* @__PURE__ */ React.createElement(Text, { color: "gray", bold: true }, "Exiting agent...") : actionInProgress === "switching" ? /* @__PURE__ */ React.createElement(Text, { color: "gray", bold: true }, "Switching to local mode...") : confirmationMode === "exit" ? /* @__PURE__ */ React.createElement(Text, { color: "red", bold: true }, "\u26A0\uFE0F Press Ctrl-C again to exit the agent") : confirmationMode === "switch" ? /* @__PURE__ */ React.createElement(Text, { color: "yellow", bold: true }, "\u23F8\uFE0F Press space again to switch to local mode") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(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(Text, { color: "gray", dimColor: true }, "Debug logs: ", logPath))
316
+ /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", alignItems: "center" }, actionInProgress === "exiting" ? /* @__PURE__ */ React.createElement(Text, { color: "gray", bold: true }, "Exiting agent...") : confirmationMode === "exit" ? /* @__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))
597
317
  ));
598
318
  };
599
319
 
@@ -845,6 +565,152 @@ class ReasoningProcessor extends BaseReasoningProcessor {
845
565
  }
846
566
  }
847
567
 
568
+ class ConversationHistory {
569
+ messages = [];
570
+ maxMessages;
571
+ maxCharacters;
572
+ constructor(options = {}) {
573
+ this.maxMessages = options.maxMessages ?? 20;
574
+ this.maxCharacters = options.maxCharacters ?? 5e4;
575
+ }
576
+ isDuplicate(role, content) {
577
+ if (this.messages.length === 0) {
578
+ return false;
579
+ }
580
+ for (let index = this.messages.length - 1; index >= 0; index -= 1) {
581
+ const message = this.messages[index];
582
+ if (message.role !== role) {
583
+ continue;
584
+ }
585
+ const normalizedIncoming = content.trim().replace(/\s+/g, " ");
586
+ const normalizedExisting = message.content.replace(/\s+/g, " ");
587
+ return normalizedIncoming === normalizedExisting;
588
+ }
589
+ return false;
590
+ }
591
+ addUserMessage(content) {
592
+ this.addMessage("user", content);
593
+ }
594
+ addAssistantMessage(content) {
595
+ this.addMessage("assistant", content);
596
+ }
597
+ hasHistory() {
598
+ return this.messages.length > 0;
599
+ }
600
+ size() {
601
+ return this.messages.length;
602
+ }
603
+ clear() {
604
+ this.messages = [];
605
+ logger.debug("[CodexConversationHistory] History cleared");
606
+ }
607
+ getContextForNewSession() {
608
+ if (this.messages.length === 0) {
609
+ return "";
610
+ }
611
+ const formattedMessages = this.messages.map((message) => {
612
+ const role = message.role === "user" ? "User" : "Assistant";
613
+ const content = message.content.length > 2e3 ? `${message.content.slice(0, 2e3)}... [truncated]` : message.content;
614
+ return `${role}: ${content}`;
615
+ }).join("\n\n");
616
+ return [
617
+ "[PREVIOUS CONVERSATION CONTEXT]",
618
+ "Continue from the prior Codex session using the conversation below as context.",
619
+ "",
620
+ formattedMessages,
621
+ "",
622
+ "[END OF PREVIOUS CONTEXT]",
623
+ ""
624
+ ].join("\n");
625
+ }
626
+ getSummary() {
627
+ const totalChars = this.messages.reduce((sum, message) => sum + message.content.length, 0);
628
+ const userCount = this.messages.filter((message) => message.role === "user").length;
629
+ const assistantCount = this.messages.filter((message) => message.role === "assistant").length;
630
+ return `${this.messages.length} messages (${userCount} user, ${assistantCount} assistant), ${totalChars} chars`;
631
+ }
632
+ addMessage(role, content) {
633
+ const trimmedContent = content.trim();
634
+ if (!trimmedContent) {
635
+ return;
636
+ }
637
+ if (this.isDuplicate(role, trimmedContent)) {
638
+ logger.debug(`[CodexConversationHistory] Skipping duplicate ${role} message (${trimmedContent.length} chars)`);
639
+ return;
640
+ }
641
+ this.messages.push({
642
+ role,
643
+ content: trimmedContent,
644
+ timestamp: Date.now()
645
+ });
646
+ this.trimHistory();
647
+ logger.debug(`[CodexConversationHistory] Added ${role} message (${trimmedContent.length} chars), total: ${this.messages.length}`);
648
+ }
649
+ trimHistory() {
650
+ while (this.messages.length > this.maxMessages) {
651
+ this.messages.shift();
652
+ }
653
+ let totalChars = this.messages.reduce((sum, message) => sum + message.content.length, 0);
654
+ while (totalChars > this.maxCharacters && this.messages.length > 1) {
655
+ const removed = this.messages.shift();
656
+ if (removed) {
657
+ totalChars -= removed.content.length;
658
+ }
659
+ }
660
+ }
661
+ }
662
+
663
+ function resolveCodexAcpExecutionMode(mode) {
664
+ const approvalPolicy = (() => {
665
+ switch (mode.permissionMode) {
666
+ case "default":
667
+ return "on-request";
668
+ case "read-only":
669
+ return "on-request";
670
+ case "safe-yolo":
671
+ return "on-request";
672
+ case "yolo":
673
+ return "never";
674
+ case "bypassPermissions":
675
+ return "never";
676
+ case "acceptEdits":
677
+ return "on-request";
678
+ case "plan":
679
+ return "on-request";
680
+ default:
681
+ return "on-request";
682
+ }
683
+ })();
684
+ const sandbox = (() => {
685
+ switch (mode.permissionMode) {
686
+ case "default":
687
+ return "workspace-write";
688
+ case "read-only":
689
+ return "read-only";
690
+ case "safe-yolo":
691
+ return "workspace-write";
692
+ case "yolo":
693
+ return "danger-full-access";
694
+ case "bypassPermissions":
695
+ return "danger-full-access";
696
+ case "acceptEdits":
697
+ return "workspace-write";
698
+ case "plan":
699
+ return "read-only";
700
+ default:
701
+ return "read-only";
702
+ }
703
+ })();
704
+ return {
705
+ approvalPolicy,
706
+ sandbox,
707
+ model: mode.model
708
+ };
709
+ }
710
+ function getCodexExecutionFingerprint(mode) {
711
+ return hashObject(resolveCodexAcpExecutionMode(mode));
712
+ }
713
+
848
714
  function createAbortError() {
849
715
  const error = new Error("Operation aborted");
850
716
  error.name = "AbortError";
@@ -897,6 +763,9 @@ function normalizeCodexBackendError(error) {
897
763
  }
898
764
  return text || "Codex backend exited unexpectedly";
899
765
  }
766
+ function getCodexLegacySwitchIgnoredMessage() {
767
+ return "Codex now runs in ACP-only mode. Staying in the current session.";
768
+ }
900
769
  function shouldEmitCodexReadyAfterWait(opts) {
901
770
  if (opts.readyAlreadySent) {
902
771
  return false;
@@ -923,58 +792,10 @@ function handleIncomingCodexMessageDuringRemoteTurn(opts) {
923
792
  }
924
793
  return total > 0 || interruptedTurn;
925
794
  }
926
- function resolveCodexAcpExecutionMode(mode) {
927
- const approvalPolicy = (() => {
928
- switch (mode.permissionMode) {
929
- case "default":
930
- return "on-request";
931
- case "read-only":
932
- return "on-request";
933
- case "safe-yolo":
934
- return "on-request";
935
- case "yolo":
936
- return "never";
937
- case "bypassPermissions":
938
- return "never";
939
- case "acceptEdits":
940
- return "on-request";
941
- case "plan":
942
- return "on-request";
943
- default:
944
- return "on-request";
945
- }
946
- })();
947
- const sandbox = (() => {
948
- switch (mode.permissionMode) {
949
- case "default":
950
- return "workspace-write";
951
- case "read-only":
952
- return "read-only";
953
- case "safe-yolo":
954
- return "workspace-write";
955
- case "yolo":
956
- return "danger-full-access";
957
- case "bypassPermissions":
958
- return "danger-full-access";
959
- case "acceptEdits":
960
- return "workspace-write";
961
- case "plan":
962
- return "read-only";
963
- default:
964
- return "read-only";
965
- }
966
- })();
967
- return {
968
- approvalPolicy,
969
- sandbox,
970
- model: mode.model
971
- };
972
- }
973
795
  async function codexRemoteLauncher(session) {
974
796
  const messageBuffer = new MessageBuffer();
975
797
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
976
798
  let inkInstance = null;
977
- let exitReason = null;
978
799
  let shouldExit = false;
979
800
  let abortController = new AbortController();
980
801
  let turnInFlight = false;
@@ -987,8 +808,10 @@ async function codexRemoteLauncher(session) {
987
808
  let accumulatedResponse = "";
988
809
  let isResponseInProgress = false;
989
810
  let taskStartedSent = false;
811
+ let shouldInjectHistoryOnNextSession = false;
990
812
  const permissionHandler = new CodexPermissionHandler(session.client);
991
813
  const selectionHandler = new CodexSelectionHandler(session.client);
814
+ const conversationHistory = new ConversationHistory({ maxMessages: 20, maxCharacters: 5e4 });
992
815
  const reasoningProcessor = new ReasoningProcessor((message) => {
993
816
  session.runtimeSession.sendCodexMessage(message);
994
817
  });
@@ -1041,10 +864,26 @@ async function codexRemoteLauncher(session) {
1041
864
  logger.debug("[Codex] Error disposing ACP backend:", error);
1042
865
  }
1043
866
  };
867
+ const emitStatusMessage = (message) => {
868
+ messageBuffer.addMessage(message, "status");
869
+ session.runtimeSession.sendSessionEvent({ type: "message", message });
870
+ };
871
+ const queueHistoryInjectionForRestart = (reason) => {
872
+ messageBuffer.addMessage("\u2550".repeat(40), "status");
873
+ if (conversationHistory.hasHistory()) {
874
+ shouldInjectHistoryOnNextSession = true;
875
+ const message = `${reason} Preserving ${conversationHistory.size()} earlier messages of context.`;
876
+ emitStatusMessage(message);
877
+ logger.debug(`[Codex] Will inject conversation history after restart: ${conversationHistory.getSummary()}`);
878
+ return;
879
+ }
880
+ emitStatusMessage(reason);
881
+ };
1044
882
  const emitFinalAssistantMessage = () => {
1045
883
  if (!accumulatedResponse.trim()) {
1046
884
  return;
1047
885
  }
886
+ conversationHistory.addAssistantMessage(accumulatedResponse);
1048
887
  session.runtimeSession.sendCodexMessage({
1049
888
  type: "message",
1050
889
  message: accumulatedResponse,
@@ -1259,11 +1098,9 @@ async function codexRemoteLauncher(session) {
1259
1098
  return result.backend;
1260
1099
  };
1261
1100
  const handleSwitchToLocal = async () => {
1262
- logger.debug("[Codex] Switching to local from RPC");
1263
- session.pinLocalMode();
1264
- exitReason = "switch";
1265
- shouldExit = true;
1266
- await handleAbort();
1101
+ const message = getCodexLegacySwitchIgnoredMessage();
1102
+ logger.debug("[Codex] Ignoring legacy switch request because Codex is ACP-only");
1103
+ emitStatusMessage(message);
1267
1104
  };
1268
1105
  session.setPendingInteractionSuperseder((reason = INTERACTION_SUPERSEDED_ERROR) => {
1269
1106
  return handleIncomingCodexMessageDuringRemoteTurn({
@@ -1324,17 +1161,9 @@ async function codexRemoteLauncher(session) {
1324
1161
  inkInstance = render(React.createElement(CodexDisplay, {
1325
1162
  messageBuffer,
1326
1163
  logPath: process.env.DEBUG ? session.logPath : void 0,
1327
- title: "Remote Mode - Codex Messages",
1164
+ title: "Codex Agent Messages",
1328
1165
  onExit: async () => {
1329
- logger.debug("[Codex] Switching to exit from local keyboard");
1330
- exitReason = "exit";
1331
- shouldExit = true;
1332
- await handleAbort();
1333
- },
1334
- onSwitchToLocal: async () => {
1335
- logger.debug("[Codex] Switching to local from local keyboard");
1336
- session.pinLocalMode();
1337
- exitReason = "switch";
1166
+ logger.debug("[Codex] Exiting Codex ACP session from keyboard");
1338
1167
  shouldExit = true;
1339
1168
  await handleAbort();
1340
1169
  }
@@ -1374,8 +1203,7 @@ async function codexRemoteLauncher(session) {
1374
1203
  break;
1375
1204
  }
1376
1205
  if (wasSessionCreated && currentModeHash && message.hash !== currentModeHash) {
1377
- messageBuffer.addMessage("\u2550".repeat(40), "status");
1378
- messageBuffer.addMessage("Starting new Codex session (mode changed)...", "status");
1206
+ queueHistoryInjectionForRestart("Starting new Codex session (execution settings changed)...");
1379
1207
  await disposeBackend();
1380
1208
  session.clearSessionId();
1381
1209
  currentModeHash = null;
@@ -1402,27 +1230,33 @@ async function codexRemoteLauncher(session) {
1402
1230
  session.onSessionFound(sessionId);
1403
1231
  }
1404
1232
  session.onThinkingChange(true);
1405
- await activeBackend.sendPrompt(acpSessionId, message.message);
1233
+ let promptToSend = message.message;
1234
+ if (shouldInjectHistoryOnNextSession && conversationHistory.hasHistory()) {
1235
+ const historyContext = conversationHistory.getContextForNewSession();
1236
+ promptToSend = historyContext + promptToSend;
1237
+ logger.debug(`[Codex] Injected conversation history context (${historyContext.length} chars)`);
1238
+ }
1239
+ conversationHistory.addUserMessage(message.message);
1240
+ await activeBackend.sendPrompt(acpSessionId, promptToSend);
1406
1241
  await waitForResponseCompleteWithAbort(activeBackend, turnSignal);
1407
1242
  reasoningProcessor.completeCurrent();
1243
+ shouldInjectHistoryOnNextSession = false;
1408
1244
  } catch (error) {
1409
1245
  logger.warn("Error in codex ACP session:", error);
1410
1246
  const isAbortError = error instanceof Error && error.name === "AbortError";
1411
- const isExpectedInterruption = isAbortError || turnSignal.aborted || shouldExit || exitReason === "switch";
1412
- if (exitReason === "switch") {
1413
- messageBuffer.addMessage("Switching to local mode...", "status");
1414
- session.runtimeSession.sendSessionEvent({ type: "message", message: "Switching to local mode..." });
1415
- } else if (isExpectedInterruption) {
1247
+ const isExpectedInterruption = isAbortError || turnSignal.aborted || shouldExit;
1248
+ if (isExpectedInterruption) {
1416
1249
  session.runtimeSession.sendCodexMessage({
1417
1250
  type: "turn_aborted",
1418
1251
  id: randomUUID()
1419
1252
  });
1420
- messageBuffer.addMessage("Aborted by user", "status");
1421
- session.runtimeSession.sendSessionEvent({ type: "message", message: "Aborted by user" });
1253
+ emitStatusMessage("Aborted by user");
1422
1254
  } else {
1423
1255
  const errorMessage = normalizeCodexBackendError(error);
1424
- messageBuffer.addMessage(errorMessage, "status");
1425
- session.runtimeSession.sendSessionEvent({ type: "message", message: errorMessage });
1256
+ emitStatusMessage(errorMessage);
1257
+ if (conversationHistory.hasHistory()) {
1258
+ shouldInjectHistoryOnNextSession = true;
1259
+ }
1426
1260
  await disposeBackend();
1427
1261
  session.clearSessionId();
1428
1262
  }
@@ -1476,33 +1310,16 @@ async function codexRemoteLauncher(session) {
1476
1310
  }
1477
1311
  messageBuffer.clear();
1478
1312
  }
1479
- logger.debug(`[Codex] ACP remote launcher returning: ${exitReason || "exit"}`);
1480
- return exitReason || "exit";
1313
+ logger.debug("[Codex] ACP remote launcher returning: exit");
1314
+ return "exit";
1481
1315
  }
1482
1316
 
1483
1317
  async function codexLoop(opts) {
1484
- let mode = opts.startingMode ?? "local";
1485
- await opts.session.onModeChange(mode);
1486
- while (true) {
1487
- logger.debug(`[codex-loop] Iteration with mode: ${mode}`);
1488
- if (mode === "local") {
1489
- const result = await codexLocalLauncher(opts.session);
1490
- if (result.type === "switch") {
1491
- opts.session.clearLocalModePin();
1492
- mode = "remote";
1493
- await opts.session.onModeChange(mode);
1494
- continue;
1495
- }
1496
- return result.code;
1497
- }
1498
- const reason = await codexRemoteLauncher(opts.session);
1499
- if (reason === "switch") {
1500
- mode = "local";
1501
- await opts.session.onModeChange(mode);
1502
- continue;
1503
- }
1504
- return 0;
1505
- }
1318
+ const displayMode = opts.startingMode ?? "local";
1319
+ await opts.session.onModeChange(displayMode);
1320
+ logger.debug(`[codex-loop] Starting ACP-only Codex launcher with display mode: ${displayMode}`);
1321
+ await codexRemoteLauncher(opts.session);
1322
+ return 0;
1506
1323
  }
1507
1324
 
1508
1325
  function supportsAgentStateUpdateEvents(sessionClient) {
@@ -1543,16 +1360,17 @@ async function syncControlledByUserState(sessionClient, controlledByUser) {
1543
1360
  });
1544
1361
  }
1545
1362
  function shouldSupersedeCodexPendingInteractions(opts) {
1546
- return (opts.currentMode ?? opts.startingMode) === "remote";
1363
+ return true;
1547
1364
  }
1548
1365
  async function runCodex(opts) {
1549
1366
  const sessionTag = randomUUID();
1550
1367
  connectionState.setBackend("Codex");
1551
- if (opts.startedBy === "daemon" && opts.startingMode === "local") {
1368
+ const requestedStartingMode = opts.startingMode ?? (opts.startedBy === "daemon" ? "remote" : "local");
1369
+ if (opts.startedBy === "daemon" && requestedStartingMode === "local") {
1552
1370
  throw new Error("Daemon-spawned Codex sessions cannot use local mode.");
1553
1371
  }
1554
1372
  const api = await ApiClient.create(opts.credentials);
1555
- logger.debug(`[codex] Starting with options: startedBy=${opts.startedBy || "terminal"}, startingMode=${opts.startingMode || "local"}`);
1373
+ logger.debug(`[codex] Starting with options: startedBy=${opts.startedBy || "terminal"}, requestedStartingMode=${requestedStartingMode}, executionMode=acp-only`);
1556
1374
  const settings = await readSettings();
1557
1375
  const machineId = settings?.machineId;
1558
1376
  if (!machineId) {
@@ -1601,10 +1419,7 @@ async function runCodex(opts) {
1601
1419
  logger.debug("[START] Failed to report to daemon:", error);
1602
1420
  }
1603
1421
  }
1604
- const messageQueue = new MessageQueue2((mode) => hashObject({
1605
- permissionMode: mode.permissionMode,
1606
- model: mode.model
1607
- }));
1422
+ const messageQueue = new MessageQueue2(getCodexExecutionFingerprint);
1608
1423
  let currentPermissionMode;
1609
1424
  let currentModel;
1610
1425
  sessionClient.onUserMessage((message) => {
@@ -1636,7 +1451,7 @@ async function runCodex(opts) {
1636
1451
  path: process.cwd(),
1637
1452
  logPath: logger.logFilePath,
1638
1453
  sessionId: null,
1639
- mode: opts.startingMode ?? "local",
1454
+ mode: requestedStartingMode,
1640
1455
  messageQueue,
1641
1456
  codexArgs: opts.codexArgs,
1642
1457
  onModeChange: async (mode) => {
@@ -1647,7 +1462,7 @@ async function runCodex(opts) {
1647
1462
  try {
1648
1463
  const exitCode = await codexLoop({
1649
1464
  session: codexSession,
1650
- startingMode: opts.startingMode ?? "local"
1465
+ startingMode: requestedStartingMode
1651
1466
  });
1652
1467
  sessionClient.sendSessionDeath();
1653
1468
  await sessionClient.flush();