happy-imou-cloud 2.0.2 → 2.0.4

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-B6tJ_eL5.cjs → BaseReasoningProcessor-DEEfNi5Y.cjs} +4 -4
  2. package/dist/{BaseReasoningProcessor-D8VhEbs2.mjs → BaseReasoningProcessor-Di1yEMMv.mjs} +2 -2
  3. package/dist/{api-MYhAGPLn.mjs → api-CIHTNilH.mjs} +2 -2
  4. package/dist/{api-D2Njw9Im.cjs → api-CyJG1mr6.cjs} +43 -43
  5. package/dist/{command-nmK6O-ab.mjs → command-BERqmFB0.mjs} +3 -3
  6. package/dist/{command-CVldr51S.cjs → command-CPlJKXDn.cjs} +3 -3
  7. package/dist/{index-Bg-YziG2.cjs → index-1zlH6s7a.cjs} +313 -118
  8. package/dist/{index-B97L7qLD.mjs → index-vNYxNqVZ.mjs} +226 -31
  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-D_2GkJAO.cjs → persistence-BeFVx6kI.cjs} +28 -28
  14. package/dist/{persistence-Dkm7rm8k.mjs → persistence-sLEqV8vk.mjs} +1 -1
  15. package/dist/{registerKillSessionHandler-BAXmJQRt.cjs → registerKillSessionHandler-CCxqGFjZ.cjs} +2 -2
  16. package/dist/{registerKillSessionHandler-5GbrO0FM.mjs → registerKillSessionHandler-uVHqIC4h.mjs} +2 -2
  17. package/dist/{runClaude-Cii3R2Fv.mjs → runClaude-Dl9nIRIg.mjs} +25 -5
  18. package/dist/{runClaude-B-GNEkKg.cjs → runClaude-Dz-PCSvb.cjs} +53 -33
  19. package/dist/{runCodex-CPHyGwj9.cjs → runCodex-BtZplK1R.cjs} +275 -408
  20. package/dist/{runCodex-C--ZwAhl.mjs → runCodex-DgKKw3IU.mjs} +273 -409
  21. package/dist/{runGemini-CQp7Nuzn.mjs → runGemini-CM1v3I24.mjs} +10 -8
  22. package/dist/{runGemini-DaDz1bzQ.cjs → runGemini-DUyH311Z.cjs} +10 -8
  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-CIHTNilH.mjs';
3
+ import { readSettings } from './persistence-sLEqV8vk.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-vNYxNqVZ.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-Di1yEMMv.mjs';
6
+ import { h as hashObject, a as MessageBuffer, r as registerKillSessionHandler, M as MessageQueue2 } from './registerKillSessionHandler-uVHqIC4h.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";
@@ -870,7 +736,8 @@ function normalizeCodexBackendError(error) {
870
736
  const text = formatDisplayMessage(error).trim();
871
737
  const stderrText = record ? formatDisplayMessage(record.stderr).trim() : "";
872
738
  const detailText = record ? formatDisplayMessage(record.detail).trim() : "";
873
- const searchableText = [text, stderrText, detailText].filter(Boolean).join("\n");
739
+ const dataText = record ? formatDisplayMessage(record.data).trim() : "";
740
+ const searchableText = [text, stderrText, detailText, dataText].filter(Boolean).join("\n");
874
741
  const prefix = typeof error === "object" && error !== null ? [
875
742
  record?.code !== void 0 && record?.code !== null ? `[code=${String(record.code)}]` : "",
876
743
  record?.status !== void 0 && record?.status !== null ? `[status=${String(record.status)}]` : ""
@@ -883,6 +750,9 @@ function normalizeCodexBackendError(error) {
883
750
  const hint = "The configured Codex ACP command does not speak the ACP protocol. Make sure HAPPY_CODEX_ACP_BIN points to codex-acp, not the codex CLI.";
884
751
  return prefix ? `${prefix} ${hint}` : hint;
885
752
  }
753
+ if (typeof record?.message === "string" && record.message.trim().toLowerCase() === "internal error" && dataText) {
754
+ return prefix ? `${prefix} ${dataText}` : dataText;
755
+ }
886
756
  if (error instanceof Error && text) {
887
757
  return text;
888
758
  }
@@ -897,6 +767,9 @@ function normalizeCodexBackendError(error) {
897
767
  }
898
768
  return text || "Codex backend exited unexpectedly";
899
769
  }
770
+ function getCodexLegacySwitchIgnoredMessage() {
771
+ return "Codex now runs in ACP-only mode. Staying in the current session.";
772
+ }
900
773
  function shouldEmitCodexReadyAfterWait(opts) {
901
774
  if (opts.readyAlreadySent) {
902
775
  return false;
@@ -923,58 +796,10 @@ function handleIncomingCodexMessageDuringRemoteTurn(opts) {
923
796
  }
924
797
  return total > 0 || interruptedTurn;
925
798
  }
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
799
  async function codexRemoteLauncher(session) {
974
800
  const messageBuffer = new MessageBuffer();
975
801
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
976
802
  let inkInstance = null;
977
- let exitReason = null;
978
803
  let shouldExit = false;
979
804
  let abortController = new AbortController();
980
805
  let turnInFlight = false;
@@ -987,8 +812,10 @@ async function codexRemoteLauncher(session) {
987
812
  let accumulatedResponse = "";
988
813
  let isResponseInProgress = false;
989
814
  let taskStartedSent = false;
815
+ let shouldInjectHistoryOnNextSession = false;
990
816
  const permissionHandler = new CodexPermissionHandler(session.client);
991
817
  const selectionHandler = new CodexSelectionHandler(session.client);
818
+ const conversationHistory = new ConversationHistory({ maxMessages: 20, maxCharacters: 5e4 });
992
819
  const reasoningProcessor = new ReasoningProcessor((message) => {
993
820
  session.runtimeSession.sendCodexMessage(message);
994
821
  });
@@ -1041,10 +868,34 @@ async function codexRemoteLauncher(session) {
1041
868
  logger.debug("[Codex] Error disposing ACP backend:", error);
1042
869
  }
1043
870
  };
871
+ const emitStatusMessage = (message) => {
872
+ messageBuffer.addMessage(message, "status");
873
+ session.runtimeSession.sendSessionEvent({ type: "message", message });
874
+ };
875
+ const emitUserVisibleErrorMessage = (message) => {
876
+ emitStatusMessage(message);
877
+ session.runtimeSession.sendCodexMessage({
878
+ type: "message",
879
+ message,
880
+ id: randomUUID()
881
+ });
882
+ };
883
+ const queueHistoryInjectionForRestart = (reason) => {
884
+ messageBuffer.addMessage("\u2550".repeat(40), "status");
885
+ if (conversationHistory.hasHistory()) {
886
+ shouldInjectHistoryOnNextSession = true;
887
+ const message = `${reason} Preserving ${conversationHistory.size()} earlier messages of context.`;
888
+ emitStatusMessage(message);
889
+ logger.debug(`[Codex] Will inject conversation history after restart: ${conversationHistory.getSummary()}`);
890
+ return;
891
+ }
892
+ emitStatusMessage(reason);
893
+ };
1044
894
  const emitFinalAssistantMessage = () => {
1045
895
  if (!accumulatedResponse.trim()) {
1046
896
  return;
1047
897
  }
898
+ conversationHistory.addAssistantMessage(accumulatedResponse);
1048
899
  session.runtimeSession.sendCodexMessage({
1049
900
  type: "message",
1050
901
  message: accumulatedResponse,
@@ -1123,7 +974,8 @@ async function codexRemoteLauncher(session) {
1123
974
  type: "tool-call-result",
1124
975
  callId: msg.callId,
1125
976
  output: msg.result,
1126
- id: randomUUID()
977
+ id: randomUUID(),
978
+ isError
1127
979
  });
1128
980
  return;
1129
981
  }
@@ -1201,7 +1053,8 @@ async function codexRemoteLauncher(session) {
1201
1053
  stderr: msg.stderr,
1202
1054
  success: msg.success
1203
1055
  },
1204
- id: randomUUID()
1056
+ id: randomUUID(),
1057
+ isError: !msg.success
1205
1058
  });
1206
1059
  return;
1207
1060
  }
@@ -1259,11 +1112,9 @@ async function codexRemoteLauncher(session) {
1259
1112
  return result.backend;
1260
1113
  };
1261
1114
  const handleSwitchToLocal = async () => {
1262
- logger.debug("[Codex] Switching to local from RPC");
1263
- session.pinLocalMode();
1264
- exitReason = "switch";
1265
- shouldExit = true;
1266
- await handleAbort();
1115
+ const message = getCodexLegacySwitchIgnoredMessage();
1116
+ logger.debug("[Codex] Ignoring legacy switch request because Codex is ACP-only");
1117
+ emitStatusMessage(message);
1267
1118
  };
1268
1119
  session.setPendingInteractionSuperseder((reason = INTERACTION_SUPERSEDED_ERROR) => {
1269
1120
  return handleIncomingCodexMessageDuringRemoteTurn({
@@ -1324,17 +1175,9 @@ async function codexRemoteLauncher(session) {
1324
1175
  inkInstance = render(React.createElement(CodexDisplay, {
1325
1176
  messageBuffer,
1326
1177
  logPath: process.env.DEBUG ? session.logPath : void 0,
1327
- title: "Remote Mode - Codex Messages",
1178
+ title: "Codex Agent Messages",
1328
1179
  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";
1180
+ logger.debug("[Codex] Exiting Codex ACP session from keyboard");
1338
1181
  shouldExit = true;
1339
1182
  await handleAbort();
1340
1183
  }
@@ -1374,8 +1217,7 @@ async function codexRemoteLauncher(session) {
1374
1217
  break;
1375
1218
  }
1376
1219
  if (wasSessionCreated && currentModeHash && message.hash !== currentModeHash) {
1377
- messageBuffer.addMessage("\u2550".repeat(40), "status");
1378
- messageBuffer.addMessage("Starting new Codex session (mode changed)...", "status");
1220
+ queueHistoryInjectionForRestart("Starting new Codex session (execution settings changed)...");
1379
1221
  await disposeBackend();
1380
1222
  session.clearSessionId();
1381
1223
  currentModeHash = null;
@@ -1402,27 +1244,33 @@ async function codexRemoteLauncher(session) {
1402
1244
  session.onSessionFound(sessionId);
1403
1245
  }
1404
1246
  session.onThinkingChange(true);
1405
- await activeBackend.sendPrompt(acpSessionId, message.message);
1247
+ let promptToSend = message.message;
1248
+ if (shouldInjectHistoryOnNextSession && conversationHistory.hasHistory()) {
1249
+ const historyContext = conversationHistory.getContextForNewSession();
1250
+ promptToSend = historyContext + promptToSend;
1251
+ logger.debug(`[Codex] Injected conversation history context (${historyContext.length} chars)`);
1252
+ }
1253
+ conversationHistory.addUserMessage(message.message);
1254
+ await activeBackend.sendPrompt(acpSessionId, promptToSend);
1406
1255
  await waitForResponseCompleteWithAbort(activeBackend, turnSignal);
1407
1256
  reasoningProcessor.completeCurrent();
1257
+ shouldInjectHistoryOnNextSession = false;
1408
1258
  } catch (error) {
1409
1259
  logger.warn("Error in codex ACP session:", error);
1410
1260
  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) {
1261
+ const isExpectedInterruption = isAbortError || turnSignal.aborted || shouldExit;
1262
+ if (isExpectedInterruption) {
1416
1263
  session.runtimeSession.sendCodexMessage({
1417
1264
  type: "turn_aborted",
1418
1265
  id: randomUUID()
1419
1266
  });
1420
- messageBuffer.addMessage("Aborted by user", "status");
1421
- session.runtimeSession.sendSessionEvent({ type: "message", message: "Aborted by user" });
1267
+ emitStatusMessage("Aborted by user");
1422
1268
  } else {
1423
1269
  const errorMessage = normalizeCodexBackendError(error);
1424
- messageBuffer.addMessage(errorMessage, "status");
1425
- session.runtimeSession.sendSessionEvent({ type: "message", message: errorMessage });
1270
+ emitUserVisibleErrorMessage(errorMessage);
1271
+ if (conversationHistory.hasHistory()) {
1272
+ shouldInjectHistoryOnNextSession = true;
1273
+ }
1426
1274
  await disposeBackend();
1427
1275
  session.clearSessionId();
1428
1276
  }
@@ -1476,33 +1324,16 @@ async function codexRemoteLauncher(session) {
1476
1324
  }
1477
1325
  messageBuffer.clear();
1478
1326
  }
1479
- logger.debug(`[Codex] ACP remote launcher returning: ${exitReason || "exit"}`);
1480
- return exitReason || "exit";
1327
+ logger.debug("[Codex] ACP remote launcher returning: exit");
1328
+ return "exit";
1481
1329
  }
1482
1330
 
1483
1331
  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
- }
1332
+ const displayMode = opts.startingMode ?? "local";
1333
+ await opts.session.onModeChange(displayMode);
1334
+ logger.debug(`[codex-loop] Starting ACP-only Codex launcher with display mode: ${displayMode}`);
1335
+ await codexRemoteLauncher(opts.session);
1336
+ return 0;
1506
1337
  }
1507
1338
 
1508
1339
  function supportsAgentStateUpdateEvents(sessionClient) {
@@ -1543,16 +1374,46 @@ async function syncControlledByUserState(sessionClient, controlledByUser) {
1543
1374
  });
1544
1375
  }
1545
1376
  function shouldSupersedeCodexPendingInteractions(opts) {
1546
- return (opts.currentMode ?? opts.startingMode) === "remote";
1377
+ return true;
1378
+ }
1379
+ function resolveInitialCodexPermissionMode(opts) {
1380
+ if (opts.permissionMode) {
1381
+ return opts.permissionMode;
1382
+ }
1383
+ const startingMode = opts.startingMode ?? (opts.startedBy === "daemon" ? "remote" : "local");
1384
+ if (opts.startedBy === "daemon" && startingMode === "remote") {
1385
+ return "yolo";
1386
+ }
1387
+ return void 0;
1388
+ }
1389
+ function resolveQueuedCodexPermissionMode(opts) {
1390
+ if (opts.preserveCurrentOnDefault && opts.messagePermissionMode === "default" && opts.currentPermissionMode) {
1391
+ return opts.currentPermissionMode;
1392
+ }
1393
+ return opts.messagePermissionMode ?? opts.currentPermissionMode ?? "default";
1394
+ }
1395
+ function resolveIncomingCodexPermissionMode(opts) {
1396
+ const resolvedPermissionMode = resolveQueuedCodexPermissionMode(opts);
1397
+ return {
1398
+ resolvedPermissionMode,
1399
+ nextCurrentPermissionMode: resolvedPermissionMode
1400
+ };
1547
1401
  }
1548
1402
  async function runCodex(opts) {
1549
1403
  const sessionTag = randomUUID();
1550
1404
  connectionState.setBackend("Codex");
1551
- if (opts.startedBy === "daemon" && opts.startingMode === "local") {
1405
+ const requestedStartingMode = opts.startingMode ?? (opts.startedBy === "daemon" ? "remote" : "local");
1406
+ const initialPermissionMode = resolveInitialCodexPermissionMode({
1407
+ permissionMode: opts.permissionMode,
1408
+ startedBy: opts.startedBy,
1409
+ startingMode: requestedStartingMode
1410
+ });
1411
+ const preserveCurrentPermissionModeForRemoteDefault = opts.startedBy === "daemon" && requestedStartingMode === "remote";
1412
+ if (opts.startedBy === "daemon" && requestedStartingMode === "local") {
1552
1413
  throw new Error("Daemon-spawned Codex sessions cannot use local mode.");
1553
1414
  }
1554
1415
  const api = await ApiClient.create(opts.credentials);
1555
- logger.debug(`[codex] Starting with options: startedBy=${opts.startedBy || "terminal"}, startingMode=${opts.startingMode || "local"}`);
1416
+ logger.debug(`[codex] Starting with options: startedBy=${opts.startedBy || "terminal"}, requestedStartingMode=${requestedStartingMode}, executionMode=acp-only`);
1556
1417
  const settings = await readSettings();
1557
1418
  const machineId = settings?.machineId;
1558
1419
  if (!machineId) {
@@ -1601,18 +1462,21 @@ async function runCodex(opts) {
1601
1462
  logger.debug("[START] Failed to report to daemon:", error);
1602
1463
  }
1603
1464
  }
1604
- const messageQueue = new MessageQueue2((mode) => hashObject({
1605
- permissionMode: mode.permissionMode,
1606
- model: mode.model
1607
- }));
1608
- let currentPermissionMode;
1465
+ const messageQueue = new MessageQueue2(getCodexExecutionFingerprint);
1466
+ let currentPermissionMode = initialPermissionMode;
1609
1467
  let currentModel;
1610
1468
  sessionClient.onUserMessage((message) => {
1611
- let messagePermissionMode = currentPermissionMode;
1469
+ const previousPermissionMode = currentPermissionMode;
1470
+ let messagePermissionMode = previousPermissionMode;
1612
1471
  if (message.meta?.permissionMode) {
1613
1472
  messagePermissionMode = message.meta.permissionMode;
1614
- currentPermissionMode = messagePermissionMode;
1615
1473
  }
1474
+ const permissionResolution = resolveIncomingCodexPermissionMode({
1475
+ messagePermissionMode,
1476
+ currentPermissionMode: previousPermissionMode,
1477
+ preserveCurrentOnDefault: preserveCurrentPermissionModeForRemoteDefault
1478
+ });
1479
+ currentPermissionMode = permissionResolution.nextCurrentPermissionMode;
1616
1480
  let messageModel = currentModel;
1617
1481
  if (message.meta?.hasOwnProperty("model")) {
1618
1482
  messageModel = message.meta.model || void 0;
@@ -1626,7 +1490,7 @@ async function runCodex(opts) {
1626
1490
  codexSession?.supersedePendingInteractions(INTERACTION_SUPERSEDED_ERROR);
1627
1491
  }
1628
1492
  messageQueue.push(message.content.text, {
1629
- permissionMode: messagePermissionMode || "default",
1493
+ permissionMode: permissionResolution.resolvedPermissionMode,
1630
1494
  model: messageModel
1631
1495
  });
1632
1496
  });
@@ -1636,7 +1500,7 @@ async function runCodex(opts) {
1636
1500
  path: process.cwd(),
1637
1501
  logPath: logger.logFilePath,
1638
1502
  sessionId: null,
1639
- mode: opts.startingMode ?? "local",
1503
+ mode: requestedStartingMode,
1640
1504
  messageQueue,
1641
1505
  codexArgs: opts.codexArgs,
1642
1506
  onModeChange: async (mode) => {
@@ -1647,7 +1511,7 @@ async function runCodex(opts) {
1647
1511
  try {
1648
1512
  const exitCode = await codexLoop({
1649
1513
  session: codexSession,
1650
- startingMode: opts.startingMode ?? "local"
1514
+ startingMode: requestedStartingMode
1651
1515
  });
1652
1516
  sessionClient.sendSessionDeath();
1653
1517
  await sessionClient.flush();
@@ -1660,4 +1524,4 @@ async function runCodex(opts) {
1660
1524
  }
1661
1525
  }
1662
1526
 
1663
- export { runCodex, shouldSupersedeCodexPendingInteractions, supportsAgentStateUpdateEvents, syncControlledByUserState };
1527
+ export { resolveIncomingCodexPermissionMode, resolveInitialCodexPermissionMode, resolveQueuedCodexPermissionMode, runCodex, shouldSupersedeCodexPendingInteractions, supportsAgentStateUpdateEvents, syncControlledByUserState };