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,21 +1,19 @@
1
1
  'use strict';
2
2
 
3
3
  var node_crypto = require('node:crypto');
4
- var api = require('./api-D2Njw9Im.cjs');
5
- var persistence = require('./persistence-D_2GkJAO.cjs');
6
- var index = require('./index-Bg-YziG2.cjs');
7
- var BaseReasoningProcessor = require('./BaseReasoningProcessor-B6tJ_eL5.cjs');
8
- var registerKillSessionHandler = require('./registerKillSessionHandler-BAXmJQRt.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');
4
+ var api = require('./api-CyJG1mr6.cjs');
5
+ var persistence = require('./persistence-BeFVx6kI.cjs');
6
+ var index = require('./index-1zlH6s7a.cjs');
7
+ var BaseReasoningProcessor = require('./BaseReasoningProcessor-DEEfNi5Y.cjs');
8
+ var registerKillSessionHandler = require('./registerKillSessionHandler-CCxqGFjZ.cjs');
14
9
  var React = require('react');
15
10
  var ink = require('ink');
16
11
  require('axios');
17
12
  require('chalk');
18
13
  require('fs');
14
+ require('node:fs');
15
+ require('node:os');
16
+ require('node:path');
19
17
  require('node:events');
20
18
  require('socket.io-client');
21
19
  require('zod');
@@ -33,6 +31,7 @@ require('qrcode-terminal');
33
31
  require('node:module');
34
32
  require('open');
35
33
  require('url');
34
+ require('node:child_process');
36
35
  require('ps-list');
37
36
  require('cross-spawn');
38
37
  require('fastify');
@@ -130,7 +129,6 @@ class CodexSession {
130
129
  sessionId;
131
130
  mode;
132
131
  thinking = false;
133
- localModePinned = false;
134
132
  keepAliveInterval;
135
133
  onModeChangeCallback;
136
134
  clientSwapCallbacks = [];
@@ -190,18 +188,6 @@ class CodexSession {
190
188
  this.client.keepAlive(this.thinking, mode);
191
189
  await this.onModeChangeCallback(mode);
192
190
  };
193
- pinLocalMode = () => {
194
- this.localModePinned = true;
195
- api.logger.debug("[CodexSession] Local mode pinned");
196
- };
197
- clearLocalModePin = () => {
198
- if (!this.localModePinned) {
199
- return;
200
- }
201
- this.localModePinned = false;
202
- api.logger.debug("[CodexSession] Local mode pin cleared");
203
- };
204
- isLocalModePinned = () => this.localModePinned;
205
191
  onSessionFound = (sessionId) => {
206
192
  if (this.sessionId === sessionId) {
207
193
  return;
@@ -219,262 +205,7 @@ class CodexSession {
219
205
  };
220
206
  }
221
207
 
222
- function getCodexSessionsRoot() {
223
- const codexHomeDir = process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
224
- return path.join(codexHomeDir, "sessions");
225
- }
226
- function collectFilesRecursive(dir, acc = []) {
227
- let entries;
228
- try {
229
- entries = fs.readdirSync(dir, { withFileTypes: true });
230
- } catch {
231
- return acc;
232
- }
233
- for (const entry of entries) {
234
- const full = path.join(dir, entry.name);
235
- if (entry.isDirectory()) {
236
- collectFilesRecursive(full, acc);
237
- } else if (entry.isFile()) {
238
- acc.push(full);
239
- }
240
- }
241
- return acc;
242
- }
243
- function extractCodexSessionIdFromPath(filePath) {
244
- 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$/);
245
- return match?.[1] ?? null;
246
- }
247
- function findLatestCodexSessionIdSince(startTimeMs) {
248
- try {
249
- const candidates = collectFilesRecursive(getCodexSessionsRoot()).filter((full) => full.endsWith(".jsonl")).filter((full) => {
250
- try {
251
- return fs.statSync(full).mtimeMs >= startTimeMs;
252
- } catch {
253
- return false;
254
- }
255
- }).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
256
- for (const candidate of candidates) {
257
- const sessionId = extractCodexSessionIdFromPath(candidate);
258
- if (sessionId) {
259
- return sessionId;
260
- }
261
- }
262
- } catch {
263
- return null;
264
- }
265
- return null;
266
- }
267
-
268
- function firstExistingPath(candidates) {
269
- for (const candidate of candidates) {
270
- try {
271
- if (fs.existsSync(candidate)) {
272
- return candidate;
273
- }
274
- } catch {
275
- }
276
- }
277
- return null;
278
- }
279
- function resolveCodexExecutable() {
280
- if (process.platform === "win32") {
281
- const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
282
- const npmGlobalBin = path.join(appData, "npm");
283
- const resolved = firstExistingPath([
284
- path.join(npmGlobalBin, "codex.cmd"),
285
- path.join(npmGlobalBin, "codex.ps1"),
286
- path.join(npmGlobalBin, "codex")
287
- ]);
288
- if (resolved) {
289
- return resolved;
290
- }
291
- }
292
- return "codex";
293
- }
294
- function shouldUseShellForCodex(executable) {
295
- return process.platform === "win32" && /\.(cmd|bat|ps1)$/i.test(executable);
296
- }
297
-
298
- class CodexExitCodeError extends Error {
299
- exitCode;
300
- constructor(exitCode) {
301
- super(`Codex process exited with code: ${exitCode}`);
302
- this.name = "CodexExitCodeError";
303
- this.exitCode = exitCode;
304
- }
305
- }
306
- function terminateCodexLocalChild(pid) {
307
- if (!pid) {
308
- return;
309
- }
310
- try {
311
- if (process.platform === "win32") {
312
- node_child_process.execFileSync("taskkill", ["/F", "/T", "/PID", pid.toString()], { stdio: "ignore" });
313
- return;
314
- }
315
- process.kill(pid, "SIGTERM");
316
- } catch (error) {
317
- api.logger.debug("[CodexLocal] Failed to terminate child process", error);
318
- }
319
- }
320
- async function codexLocal(opts) {
321
- const baseArgs = [...opts.codexArgs ?? []];
322
- const args = opts.sessionId ? ["resume", opts.sessionId, ...baseArgs] : baseArgs;
323
- const startTime = Date.now();
324
- let detectedSessionId = opts.sessionId;
325
- const codexExecutable = resolveCodexExecutable();
326
- api.logger.debug(`[CodexLocal] Spawning ${codexExecutable} with args: ${JSON.stringify(args)}`);
327
- process.stdin.pause();
328
- try {
329
- await new Promise((resolve, reject) => {
330
- const child = node_child_process.spawn(codexExecutable, args, {
331
- cwd: opts.path,
332
- env: process.env,
333
- stdio: "inherit",
334
- signal: opts.abort,
335
- shell: shouldUseShellForCodex(codexExecutable)
336
- });
337
- const abortChild = () => {
338
- if (child.exitCode === null) {
339
- api.logger.debug(`[CodexLocal] Abort received, terminating child tree pid=${child.pid ?? "none"}`);
340
- terminateCodexLocalChild(child.pid);
341
- }
342
- };
343
- opts.abort.addEventListener("abort", abortChild, { once: true });
344
- const sessionPoll = setInterval(() => {
345
- if (detectedSessionId) {
346
- return;
347
- }
348
- const nextSessionId = findLatestCodexSessionIdSince(startTime);
349
- if (nextSessionId) {
350
- detectedSessionId = nextSessionId;
351
- api.logger.debug(`[CodexLocal] Detected session ID: ${nextSessionId}`);
352
- opts.onSessionFound(nextSessionId);
353
- }
354
- }, 1e3);
355
- child.on("error", (error) => {
356
- clearInterval(sessionPoll);
357
- opts.abort.removeEventListener("abort", abortChild);
358
- reject(error);
359
- });
360
- child.on("exit", (code, signal) => {
361
- clearInterval(sessionPoll);
362
- opts.abort.removeEventListener("abort", abortChild);
363
- opts.onThinkingChange?.(false);
364
- if (signal === "SIGTERM" && opts.abort.aborted) {
365
- resolve();
366
- return;
367
- }
368
- if (signal) {
369
- reject(new Error(`Codex terminated with signal: ${signal}`));
370
- return;
371
- }
372
- if (code !== 0 && code !== null) {
373
- reject(new CodexExitCodeError(code));
374
- return;
375
- }
376
- resolve();
377
- });
378
- });
379
- } finally {
380
- process.stdin.resume();
381
- opts.onThinkingChange?.(false);
382
- }
383
- return detectedSessionId;
384
- }
385
-
386
- async function codexLocalLauncher(session) {
387
- let exitReason = null;
388
- const processAbortController = new AbortController();
389
- const exitFuture = new future.Future();
390
- try {
391
- async function abort() {
392
- if (!processAbortController.signal.aborted) {
393
- processAbortController.abort();
394
- }
395
- await exitFuture.promise;
396
- }
397
- async function doSwitch() {
398
- api.logger.debug("[codex-local]: switching to remote mode");
399
- if (!exitReason) {
400
- exitReason = { type: "switch" };
401
- }
402
- await abort();
403
- }
404
- async function doAbort() {
405
- api.logger.debug("[codex-local]: abort requested");
406
- if (!exitReason) {
407
- exitReason = { type: "switch" };
408
- }
409
- session.queue.reset();
410
- await abort();
411
- }
412
- session.client.rpcHandlerManager.registerHandler("abort", doAbort);
413
- session.client.rpcHandlerManager.registerHandler("switch", doSwitch);
414
- session.queue.setOnMessage(() => {
415
- if (session.isLocalModePinned()) {
416
- api.logger.debug("[codex-local]: message arrived while local mode is pinned; staying local");
417
- return;
418
- }
419
- void doSwitch();
420
- });
421
- if (session.queue.size() > 0) {
422
- if (session.isLocalModePinned()) {
423
- api.logger.debug("[codex-local]: pending queued messages detected, but local mode is pinned; staying local");
424
- } else {
425
- return { type: "switch" };
426
- }
427
- }
428
- while (true) {
429
- if (exitReason) {
430
- return exitReason;
431
- }
432
- try {
433
- const sessionId = await codexLocal({
434
- path: session.path,
435
- sessionId: session.sessionId,
436
- abort: processAbortController.signal,
437
- codexArgs: session.codexArgs,
438
- onSessionFound: session.onSessionFound,
439
- onThinkingChange: session.onThinkingChange
440
- });
441
- if (sessionId) {
442
- session.onSessionFound(sessionId);
443
- }
444
- if (!exitReason) {
445
- exitReason = { type: "exit", code: 0 };
446
- break;
447
- }
448
- } catch (error) {
449
- api.logger.debug("[codex-local]: launch error", error);
450
- if (error instanceof CodexExitCodeError && !exitReason) {
451
- exitReason = { type: "exit", code: error.exitCode };
452
- break;
453
- }
454
- if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "EINVAL") && !exitReason) {
455
- exitReason = { type: "exit", code: 1 };
456
- session.runtimeSession.sendSessionEvent({ type: "message", message: "Codex CLI not found. Check your local Codex installation." });
457
- break;
458
- }
459
- if (!exitReason) {
460
- session.runtimeSession.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
461
- continue;
462
- }
463
- break;
464
- }
465
- }
466
- } finally {
467
- exitFuture.resolve(void 0);
468
- session.client.rpcHandlerManager.registerHandler("abort", async () => {
469
- });
470
- session.client.rpcHandlerManager.registerHandler("switch", async () => {
471
- });
472
- session.queue.setOnMessage(null);
473
- }
474
- return exitReason || { type: "exit", code: 0 };
475
- }
476
-
477
- const CodexDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal, title }) => {
208
+ const CodexDisplay = ({ messageBuffer, logPath, onExit, title }) => {
478
209
  const [messages, setMessages] = React.useState([]);
479
210
  const [confirmationMode, setConfirmationMode] = React.useState(null);
480
211
  const [actionInProgress, setActionInProgress] = React.useState(null);
@@ -523,21 +254,10 @@ const CodexDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal, title }
523
254
  }
524
255
  return;
525
256
  }
526
- if (input === " ") {
527
- if (confirmationMode === "switch") {
528
- resetConfirmation();
529
- setActionInProgress("switching");
530
- await new Promise((resolve) => setTimeout(resolve, 100));
531
- onSwitchToLocal?.();
532
- } else {
533
- setConfirmationWithTimeout("switch");
534
- }
535
- return;
536
- }
537
257
  if (confirmationMode) {
538
258
  resetConfirmation();
539
259
  }
540
- }, [confirmationMode, actionInProgress, onExit, onSwitchToLocal, setConfirmationWithTimeout, resetConfirmation]));
260
+ }, [confirmationMode, actionInProgress, onExit, setConfirmationWithTimeout, resetConfirmation]));
541
261
  const getMessageColor = (type) => {
542
262
  switch (type) {
543
263
  case "user":
@@ -589,13 +309,13 @@ const CodexDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal, title }
589
309
  {
590
310
  width: terminalWidth,
591
311
  borderStyle: "round",
592
- borderColor: actionInProgress ? "gray" : confirmationMode === "exit" ? "red" : confirmationMode === "switch" ? "yellow" : "green",
312
+ borderColor: actionInProgress ? "gray" : confirmationMode === "exit" ? "red" : "green",
593
313
  paddingX: 2,
594
314
  justifyContent: "center",
595
315
  alignItems: "center",
596
316
  flexDirection: "column"
597
317
  },
598
- /* @__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))
318
+ /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", alignItems: "center" }, actionInProgress === "exiting" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "Exiting agent...") : confirmationMode === "exit" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "red", bold: true }, "\u26A0\uFE0F Press Ctrl-C again to exit the agent") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ink.Text, { color: "green", bold: true }, "\u{1F916} Codex Agent Running \u2022 Ctrl-C to exit")), process.env.DEBUG && logPath && /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Debug logs: ", logPath))
599
319
  ));
600
320
  };
601
321
 
@@ -847,6 +567,152 @@ class ReasoningProcessor extends BaseReasoningProcessor.BaseReasoningProcessor {
847
567
  }
848
568
  }
849
569
 
570
+ class ConversationHistory {
571
+ messages = [];
572
+ maxMessages;
573
+ maxCharacters;
574
+ constructor(options = {}) {
575
+ this.maxMessages = options.maxMessages ?? 20;
576
+ this.maxCharacters = options.maxCharacters ?? 5e4;
577
+ }
578
+ isDuplicate(role, content) {
579
+ if (this.messages.length === 0) {
580
+ return false;
581
+ }
582
+ for (let index = this.messages.length - 1; index >= 0; index -= 1) {
583
+ const message = this.messages[index];
584
+ if (message.role !== role) {
585
+ continue;
586
+ }
587
+ const normalizedIncoming = content.trim().replace(/\s+/g, " ");
588
+ const normalizedExisting = message.content.replace(/\s+/g, " ");
589
+ return normalizedIncoming === normalizedExisting;
590
+ }
591
+ return false;
592
+ }
593
+ addUserMessage(content) {
594
+ this.addMessage("user", content);
595
+ }
596
+ addAssistantMessage(content) {
597
+ this.addMessage("assistant", content);
598
+ }
599
+ hasHistory() {
600
+ return this.messages.length > 0;
601
+ }
602
+ size() {
603
+ return this.messages.length;
604
+ }
605
+ clear() {
606
+ this.messages = [];
607
+ api.logger.debug("[CodexConversationHistory] History cleared");
608
+ }
609
+ getContextForNewSession() {
610
+ if (this.messages.length === 0) {
611
+ return "";
612
+ }
613
+ const formattedMessages = this.messages.map((message) => {
614
+ const role = message.role === "user" ? "User" : "Assistant";
615
+ const content = message.content.length > 2e3 ? `${message.content.slice(0, 2e3)}... [truncated]` : message.content;
616
+ return `${role}: ${content}`;
617
+ }).join("\n\n");
618
+ return [
619
+ "[PREVIOUS CONVERSATION CONTEXT]",
620
+ "Continue from the prior Codex session using the conversation below as context.",
621
+ "",
622
+ formattedMessages,
623
+ "",
624
+ "[END OF PREVIOUS CONTEXT]",
625
+ ""
626
+ ].join("\n");
627
+ }
628
+ getSummary() {
629
+ const totalChars = this.messages.reduce((sum, message) => sum + message.content.length, 0);
630
+ const userCount = this.messages.filter((message) => message.role === "user").length;
631
+ const assistantCount = this.messages.filter((message) => message.role === "assistant").length;
632
+ return `${this.messages.length} messages (${userCount} user, ${assistantCount} assistant), ${totalChars} chars`;
633
+ }
634
+ addMessage(role, content) {
635
+ const trimmedContent = content.trim();
636
+ if (!trimmedContent) {
637
+ return;
638
+ }
639
+ if (this.isDuplicate(role, trimmedContent)) {
640
+ api.logger.debug(`[CodexConversationHistory] Skipping duplicate ${role} message (${trimmedContent.length} chars)`);
641
+ return;
642
+ }
643
+ this.messages.push({
644
+ role,
645
+ content: trimmedContent,
646
+ timestamp: Date.now()
647
+ });
648
+ this.trimHistory();
649
+ api.logger.debug(`[CodexConversationHistory] Added ${role} message (${trimmedContent.length} chars), total: ${this.messages.length}`);
650
+ }
651
+ trimHistory() {
652
+ while (this.messages.length > this.maxMessages) {
653
+ this.messages.shift();
654
+ }
655
+ let totalChars = this.messages.reduce((sum, message) => sum + message.content.length, 0);
656
+ while (totalChars > this.maxCharacters && this.messages.length > 1) {
657
+ const removed = this.messages.shift();
658
+ if (removed) {
659
+ totalChars -= removed.content.length;
660
+ }
661
+ }
662
+ }
663
+ }
664
+
665
+ function resolveCodexAcpExecutionMode(mode) {
666
+ const approvalPolicy = (() => {
667
+ switch (mode.permissionMode) {
668
+ case "default":
669
+ return "on-request";
670
+ case "read-only":
671
+ return "on-request";
672
+ case "safe-yolo":
673
+ return "on-request";
674
+ case "yolo":
675
+ return "never";
676
+ case "bypassPermissions":
677
+ return "never";
678
+ case "acceptEdits":
679
+ return "on-request";
680
+ case "plan":
681
+ return "on-request";
682
+ default:
683
+ return "on-request";
684
+ }
685
+ })();
686
+ const sandbox = (() => {
687
+ switch (mode.permissionMode) {
688
+ case "default":
689
+ return "workspace-write";
690
+ case "read-only":
691
+ return "read-only";
692
+ case "safe-yolo":
693
+ return "workspace-write";
694
+ case "yolo":
695
+ return "danger-full-access";
696
+ case "bypassPermissions":
697
+ return "danger-full-access";
698
+ case "acceptEdits":
699
+ return "workspace-write";
700
+ case "plan":
701
+ return "read-only";
702
+ default:
703
+ return "read-only";
704
+ }
705
+ })();
706
+ return {
707
+ approvalPolicy,
708
+ sandbox,
709
+ model: mode.model
710
+ };
711
+ }
712
+ function getCodexExecutionFingerprint(mode) {
713
+ return registerKillSessionHandler.hashObject(resolveCodexAcpExecutionMode(mode));
714
+ }
715
+
850
716
  function createAbortError() {
851
717
  const error = new Error("Operation aborted");
852
718
  error.name = "AbortError";
@@ -872,7 +738,8 @@ function normalizeCodexBackendError(error) {
872
738
  const text = index.formatDisplayMessage(error).trim();
873
739
  const stderrText = record ? index.formatDisplayMessage(record.stderr).trim() : "";
874
740
  const detailText = record ? index.formatDisplayMessage(record.detail).trim() : "";
875
- const searchableText = [text, stderrText, detailText].filter(Boolean).join("\n");
741
+ const dataText = record ? index.formatDisplayMessage(record.data).trim() : "";
742
+ const searchableText = [text, stderrText, detailText, dataText].filter(Boolean).join("\n");
876
743
  const prefix = typeof error === "object" && error !== null ? [
877
744
  record?.code !== void 0 && record?.code !== null ? `[code=${String(record.code)}]` : "",
878
745
  record?.status !== void 0 && record?.status !== null ? `[status=${String(record.status)}]` : ""
@@ -885,6 +752,9 @@ function normalizeCodexBackendError(error) {
885
752
  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.";
886
753
  return prefix ? `${prefix} ${hint}` : hint;
887
754
  }
755
+ if (typeof record?.message === "string" && record.message.trim().toLowerCase() === "internal error" && dataText) {
756
+ return prefix ? `${prefix} ${dataText}` : dataText;
757
+ }
888
758
  if (error instanceof Error && text) {
889
759
  return text;
890
760
  }
@@ -899,6 +769,9 @@ function normalizeCodexBackendError(error) {
899
769
  }
900
770
  return text || "Codex backend exited unexpectedly";
901
771
  }
772
+ function getCodexLegacySwitchIgnoredMessage() {
773
+ return "Codex now runs in ACP-only mode. Staying in the current session.";
774
+ }
902
775
  function shouldEmitCodexReadyAfterWait(opts) {
903
776
  if (opts.readyAlreadySent) {
904
777
  return false;
@@ -925,58 +798,10 @@ function handleIncomingCodexMessageDuringRemoteTurn(opts) {
925
798
  }
926
799
  return total > 0 || interruptedTurn;
927
800
  }
928
- function resolveCodexAcpExecutionMode(mode) {
929
- const approvalPolicy = (() => {
930
- switch (mode.permissionMode) {
931
- case "default":
932
- return "on-request";
933
- case "read-only":
934
- return "on-request";
935
- case "safe-yolo":
936
- return "on-request";
937
- case "yolo":
938
- return "never";
939
- case "bypassPermissions":
940
- return "never";
941
- case "acceptEdits":
942
- return "on-request";
943
- case "plan":
944
- return "on-request";
945
- default:
946
- return "on-request";
947
- }
948
- })();
949
- const sandbox = (() => {
950
- switch (mode.permissionMode) {
951
- case "default":
952
- return "workspace-write";
953
- case "read-only":
954
- return "read-only";
955
- case "safe-yolo":
956
- return "workspace-write";
957
- case "yolo":
958
- return "danger-full-access";
959
- case "bypassPermissions":
960
- return "danger-full-access";
961
- case "acceptEdits":
962
- return "workspace-write";
963
- case "plan":
964
- return "read-only";
965
- default:
966
- return "read-only";
967
- }
968
- })();
969
- return {
970
- approvalPolicy,
971
- sandbox,
972
- model: mode.model
973
- };
974
- }
975
801
  async function codexRemoteLauncher(session) {
976
802
  const messageBuffer = new registerKillSessionHandler.MessageBuffer();
977
803
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
978
804
  let inkInstance = null;
979
- let exitReason = null;
980
805
  let shouldExit = false;
981
806
  let abortController = new AbortController();
982
807
  let turnInFlight = false;
@@ -989,8 +814,10 @@ async function codexRemoteLauncher(session) {
989
814
  let accumulatedResponse = "";
990
815
  let isResponseInProgress = false;
991
816
  let taskStartedSent = false;
817
+ let shouldInjectHistoryOnNextSession = false;
992
818
  const permissionHandler = new CodexPermissionHandler(session.client);
993
819
  const selectionHandler = new CodexSelectionHandler(session.client);
820
+ const conversationHistory = new ConversationHistory({ maxMessages: 20, maxCharacters: 5e4 });
994
821
  const reasoningProcessor = new ReasoningProcessor((message) => {
995
822
  session.runtimeSession.sendCodexMessage(message);
996
823
  });
@@ -1043,10 +870,34 @@ async function codexRemoteLauncher(session) {
1043
870
  api.logger.debug("[Codex] Error disposing ACP backend:", error);
1044
871
  }
1045
872
  };
873
+ const emitStatusMessage = (message) => {
874
+ messageBuffer.addMessage(message, "status");
875
+ session.runtimeSession.sendSessionEvent({ type: "message", message });
876
+ };
877
+ const emitUserVisibleErrorMessage = (message) => {
878
+ emitStatusMessage(message);
879
+ session.runtimeSession.sendCodexMessage({
880
+ type: "message",
881
+ message,
882
+ id: node_crypto.randomUUID()
883
+ });
884
+ };
885
+ const queueHistoryInjectionForRestart = (reason) => {
886
+ messageBuffer.addMessage("\u2550".repeat(40), "status");
887
+ if (conversationHistory.hasHistory()) {
888
+ shouldInjectHistoryOnNextSession = true;
889
+ const message = `${reason} Preserving ${conversationHistory.size()} earlier messages of context.`;
890
+ emitStatusMessage(message);
891
+ api.logger.debug(`[Codex] Will inject conversation history after restart: ${conversationHistory.getSummary()}`);
892
+ return;
893
+ }
894
+ emitStatusMessage(reason);
895
+ };
1046
896
  const emitFinalAssistantMessage = () => {
1047
897
  if (!accumulatedResponse.trim()) {
1048
898
  return;
1049
899
  }
900
+ conversationHistory.addAssistantMessage(accumulatedResponse);
1050
901
  session.runtimeSession.sendCodexMessage({
1051
902
  type: "message",
1052
903
  message: accumulatedResponse,
@@ -1125,7 +976,8 @@ async function codexRemoteLauncher(session) {
1125
976
  type: "tool-call-result",
1126
977
  callId: msg.callId,
1127
978
  output: msg.result,
1128
- id: node_crypto.randomUUID()
979
+ id: node_crypto.randomUUID(),
980
+ isError
1129
981
  });
1130
982
  return;
1131
983
  }
@@ -1203,7 +1055,8 @@ async function codexRemoteLauncher(session) {
1203
1055
  stderr: msg.stderr,
1204
1056
  success: msg.success
1205
1057
  },
1206
- id: node_crypto.randomUUID()
1058
+ id: node_crypto.randomUUID(),
1059
+ isError: !msg.success
1207
1060
  });
1208
1061
  return;
1209
1062
  }
@@ -1261,11 +1114,9 @@ async function codexRemoteLauncher(session) {
1261
1114
  return result.backend;
1262
1115
  };
1263
1116
  const handleSwitchToLocal = async () => {
1264
- api.logger.debug("[Codex] Switching to local from RPC");
1265
- session.pinLocalMode();
1266
- exitReason = "switch";
1267
- shouldExit = true;
1268
- await handleAbort();
1117
+ const message = getCodexLegacySwitchIgnoredMessage();
1118
+ api.logger.debug("[Codex] Ignoring legacy switch request because Codex is ACP-only");
1119
+ emitStatusMessage(message);
1269
1120
  };
1270
1121
  session.setPendingInteractionSuperseder((reason = BaseReasoningProcessor.INTERACTION_SUPERSEDED_ERROR) => {
1271
1122
  return handleIncomingCodexMessageDuringRemoteTurn({
@@ -1326,17 +1177,9 @@ async function codexRemoteLauncher(session) {
1326
1177
  inkInstance = ink.render(React.createElement(CodexDisplay, {
1327
1178
  messageBuffer,
1328
1179
  logPath: process.env.DEBUG ? session.logPath : void 0,
1329
- title: "Remote Mode - Codex Messages",
1180
+ title: "Codex Agent Messages",
1330
1181
  onExit: async () => {
1331
- api.logger.debug("[Codex] Switching to exit from local keyboard");
1332
- exitReason = "exit";
1333
- shouldExit = true;
1334
- await handleAbort();
1335
- },
1336
- onSwitchToLocal: async () => {
1337
- api.logger.debug("[Codex] Switching to local from local keyboard");
1338
- session.pinLocalMode();
1339
- exitReason = "switch";
1182
+ api.logger.debug("[Codex] Exiting Codex ACP session from keyboard");
1340
1183
  shouldExit = true;
1341
1184
  await handleAbort();
1342
1185
  }
@@ -1376,8 +1219,7 @@ async function codexRemoteLauncher(session) {
1376
1219
  break;
1377
1220
  }
1378
1221
  if (wasSessionCreated && currentModeHash && message.hash !== currentModeHash) {
1379
- messageBuffer.addMessage("\u2550".repeat(40), "status");
1380
- messageBuffer.addMessage("Starting new Codex session (mode changed)...", "status");
1222
+ queueHistoryInjectionForRestart("Starting new Codex session (execution settings changed)...");
1381
1223
  await disposeBackend();
1382
1224
  session.clearSessionId();
1383
1225
  currentModeHash = null;
@@ -1404,27 +1246,33 @@ async function codexRemoteLauncher(session) {
1404
1246
  session.onSessionFound(sessionId);
1405
1247
  }
1406
1248
  session.onThinkingChange(true);
1407
- await activeBackend.sendPrompt(acpSessionId, message.message);
1249
+ let promptToSend = message.message;
1250
+ if (shouldInjectHistoryOnNextSession && conversationHistory.hasHistory()) {
1251
+ const historyContext = conversationHistory.getContextForNewSession();
1252
+ promptToSend = historyContext + promptToSend;
1253
+ api.logger.debug(`[Codex] Injected conversation history context (${historyContext.length} chars)`);
1254
+ }
1255
+ conversationHistory.addUserMessage(message.message);
1256
+ await activeBackend.sendPrompt(acpSessionId, promptToSend);
1408
1257
  await waitForResponseCompleteWithAbort(activeBackend, turnSignal);
1409
1258
  reasoningProcessor.completeCurrent();
1259
+ shouldInjectHistoryOnNextSession = false;
1410
1260
  } catch (error) {
1411
1261
  api.logger.warn("Error in codex ACP session:", error);
1412
1262
  const isAbortError = error instanceof Error && error.name === "AbortError";
1413
- const isExpectedInterruption = isAbortError || turnSignal.aborted || shouldExit || exitReason === "switch";
1414
- if (exitReason === "switch") {
1415
- messageBuffer.addMessage("Switching to local mode...", "status");
1416
- session.runtimeSession.sendSessionEvent({ type: "message", message: "Switching to local mode..." });
1417
- } else if (isExpectedInterruption) {
1263
+ const isExpectedInterruption = isAbortError || turnSignal.aborted || shouldExit;
1264
+ if (isExpectedInterruption) {
1418
1265
  session.runtimeSession.sendCodexMessage({
1419
1266
  type: "turn_aborted",
1420
1267
  id: node_crypto.randomUUID()
1421
1268
  });
1422
- messageBuffer.addMessage("Aborted by user", "status");
1423
- session.runtimeSession.sendSessionEvent({ type: "message", message: "Aborted by user" });
1269
+ emitStatusMessage("Aborted by user");
1424
1270
  } else {
1425
1271
  const errorMessage = normalizeCodexBackendError(error);
1426
- messageBuffer.addMessage(errorMessage, "status");
1427
- session.runtimeSession.sendSessionEvent({ type: "message", message: errorMessage });
1272
+ emitUserVisibleErrorMessage(errorMessage);
1273
+ if (conversationHistory.hasHistory()) {
1274
+ shouldInjectHistoryOnNextSession = true;
1275
+ }
1428
1276
  await disposeBackend();
1429
1277
  session.clearSessionId();
1430
1278
  }
@@ -1478,33 +1326,16 @@ async function codexRemoteLauncher(session) {
1478
1326
  }
1479
1327
  messageBuffer.clear();
1480
1328
  }
1481
- api.logger.debug(`[Codex] ACP remote launcher returning: ${exitReason || "exit"}`);
1482
- return exitReason || "exit";
1329
+ api.logger.debug("[Codex] ACP remote launcher returning: exit");
1330
+ return "exit";
1483
1331
  }
1484
1332
 
1485
1333
  async function codexLoop(opts) {
1486
- let mode = opts.startingMode ?? "local";
1487
- await opts.session.onModeChange(mode);
1488
- while (true) {
1489
- api.logger.debug(`[codex-loop] Iteration with mode: ${mode}`);
1490
- if (mode === "local") {
1491
- const result = await codexLocalLauncher(opts.session);
1492
- if (result.type === "switch") {
1493
- opts.session.clearLocalModePin();
1494
- mode = "remote";
1495
- await opts.session.onModeChange(mode);
1496
- continue;
1497
- }
1498
- return result.code;
1499
- }
1500
- const reason = await codexRemoteLauncher(opts.session);
1501
- if (reason === "switch") {
1502
- mode = "local";
1503
- await opts.session.onModeChange(mode);
1504
- continue;
1505
- }
1506
- return 0;
1507
- }
1334
+ const displayMode = opts.startingMode ?? "local";
1335
+ await opts.session.onModeChange(displayMode);
1336
+ api.logger.debug(`[codex-loop] Starting ACP-only Codex launcher with display mode: ${displayMode}`);
1337
+ await codexRemoteLauncher(opts.session);
1338
+ return 0;
1508
1339
  }
1509
1340
 
1510
1341
  function supportsAgentStateUpdateEvents(sessionClient) {
@@ -1545,16 +1376,46 @@ async function syncControlledByUserState(sessionClient, controlledByUser) {
1545
1376
  });
1546
1377
  }
1547
1378
  function shouldSupersedeCodexPendingInteractions(opts) {
1548
- return (opts.currentMode ?? opts.startingMode) === "remote";
1379
+ return true;
1380
+ }
1381
+ function resolveInitialCodexPermissionMode(opts) {
1382
+ if (opts.permissionMode) {
1383
+ return opts.permissionMode;
1384
+ }
1385
+ const startingMode = opts.startingMode ?? (opts.startedBy === "daemon" ? "remote" : "local");
1386
+ if (opts.startedBy === "daemon" && startingMode === "remote") {
1387
+ return "yolo";
1388
+ }
1389
+ return void 0;
1390
+ }
1391
+ function resolveQueuedCodexPermissionMode(opts) {
1392
+ if (opts.preserveCurrentOnDefault && opts.messagePermissionMode === "default" && opts.currentPermissionMode) {
1393
+ return opts.currentPermissionMode;
1394
+ }
1395
+ return opts.messagePermissionMode ?? opts.currentPermissionMode ?? "default";
1396
+ }
1397
+ function resolveIncomingCodexPermissionMode(opts) {
1398
+ const resolvedPermissionMode = resolveQueuedCodexPermissionMode(opts);
1399
+ return {
1400
+ resolvedPermissionMode,
1401
+ nextCurrentPermissionMode: resolvedPermissionMode
1402
+ };
1549
1403
  }
1550
1404
  async function runCodex(opts) {
1551
1405
  const sessionTag = node_crypto.randomUUID();
1552
1406
  api.connectionState.setBackend("Codex");
1553
- if (opts.startedBy === "daemon" && opts.startingMode === "local") {
1407
+ const requestedStartingMode = opts.startingMode ?? (opts.startedBy === "daemon" ? "remote" : "local");
1408
+ const initialPermissionMode = resolveInitialCodexPermissionMode({
1409
+ permissionMode: opts.permissionMode,
1410
+ startedBy: opts.startedBy,
1411
+ startingMode: requestedStartingMode
1412
+ });
1413
+ const preserveCurrentPermissionModeForRemoteDefault = opts.startedBy === "daemon" && requestedStartingMode === "remote";
1414
+ if (opts.startedBy === "daemon" && requestedStartingMode === "local") {
1554
1415
  throw new Error("Daemon-spawned Codex sessions cannot use local mode.");
1555
1416
  }
1556
1417
  const api$1 = await api.ApiClient.create(opts.credentials);
1557
- api.logger.debug(`[codex] Starting with options: startedBy=${opts.startedBy || "terminal"}, startingMode=${opts.startingMode || "local"}`);
1418
+ api.logger.debug(`[codex] Starting with options: startedBy=${opts.startedBy || "terminal"}, requestedStartingMode=${requestedStartingMode}, executionMode=acp-only`);
1558
1419
  const settings = await persistence.readSettings();
1559
1420
  const machineId = settings?.machineId;
1560
1421
  if (!machineId) {
@@ -1603,18 +1464,21 @@ async function runCodex(opts) {
1603
1464
  api.logger.debug("[START] Failed to report to daemon:", error);
1604
1465
  }
1605
1466
  }
1606
- const messageQueue = new registerKillSessionHandler.MessageQueue2((mode) => registerKillSessionHandler.hashObject({
1607
- permissionMode: mode.permissionMode,
1608
- model: mode.model
1609
- }));
1610
- let currentPermissionMode;
1467
+ const messageQueue = new registerKillSessionHandler.MessageQueue2(getCodexExecutionFingerprint);
1468
+ let currentPermissionMode = initialPermissionMode;
1611
1469
  let currentModel;
1612
1470
  sessionClient.onUserMessage((message) => {
1613
- let messagePermissionMode = currentPermissionMode;
1471
+ const previousPermissionMode = currentPermissionMode;
1472
+ let messagePermissionMode = previousPermissionMode;
1614
1473
  if (message.meta?.permissionMode) {
1615
1474
  messagePermissionMode = message.meta.permissionMode;
1616
- currentPermissionMode = messagePermissionMode;
1617
1475
  }
1476
+ const permissionResolution = resolveIncomingCodexPermissionMode({
1477
+ messagePermissionMode,
1478
+ currentPermissionMode: previousPermissionMode,
1479
+ preserveCurrentOnDefault: preserveCurrentPermissionModeForRemoteDefault
1480
+ });
1481
+ currentPermissionMode = permissionResolution.nextCurrentPermissionMode;
1618
1482
  let messageModel = currentModel;
1619
1483
  if (message.meta?.hasOwnProperty("model")) {
1620
1484
  messageModel = message.meta.model || void 0;
@@ -1628,7 +1492,7 @@ async function runCodex(opts) {
1628
1492
  codexSession?.supersedePendingInteractions(BaseReasoningProcessor.INTERACTION_SUPERSEDED_ERROR);
1629
1493
  }
1630
1494
  messageQueue.push(message.content.text, {
1631
- permissionMode: messagePermissionMode || "default",
1495
+ permissionMode: permissionResolution.resolvedPermissionMode,
1632
1496
  model: messageModel
1633
1497
  });
1634
1498
  });
@@ -1638,7 +1502,7 @@ async function runCodex(opts) {
1638
1502
  path: process.cwd(),
1639
1503
  logPath: api.logger.logFilePath,
1640
1504
  sessionId: null,
1641
- mode: opts.startingMode ?? "local",
1505
+ mode: requestedStartingMode,
1642
1506
  messageQueue,
1643
1507
  codexArgs: opts.codexArgs,
1644
1508
  onModeChange: async (mode) => {
@@ -1649,7 +1513,7 @@ async function runCodex(opts) {
1649
1513
  try {
1650
1514
  const exitCode = await codexLoop({
1651
1515
  session: codexSession,
1652
- startingMode: opts.startingMode ?? "local"
1516
+ startingMode: requestedStartingMode
1653
1517
  });
1654
1518
  sessionClient.sendSessionDeath();
1655
1519
  await sessionClient.flush();
@@ -1662,6 +1526,9 @@ async function runCodex(opts) {
1662
1526
  }
1663
1527
  }
1664
1528
 
1529
+ exports.resolveIncomingCodexPermissionMode = resolveIncomingCodexPermissionMode;
1530
+ exports.resolveInitialCodexPermissionMode = resolveInitialCodexPermissionMode;
1531
+ exports.resolveQueuedCodexPermissionMode = resolveQueuedCodexPermissionMode;
1665
1532
  exports.runCodex = runCodex;
1666
1533
  exports.shouldSupersedeCodexPendingInteractions = shouldSupersedeCodexPendingInteractions;
1667
1534
  exports.supportsAgentStateUpdateEvents = supportsAgentStateUpdateEvents;