happy-imou-cloud 2.0.1 → 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 (27) hide show
  1. package/dist/{BaseReasoningProcessor-DQE2l7Xu.mjs → BaseReasoningProcessor-B37yOHxo.mjs} +2 -2
  2. package/dist/{BaseReasoningProcessor-01KqA3Kz.cjs → BaseReasoningProcessor-_wxlqKB8.cjs} +4 -4
  3. package/dist/{api-B8v4tczT.cjs → api-D9dIR956.cjs} +97 -44
  4. package/dist/{api-B5Ui8Fw0.mjs → api-DpQIC-DJ.mjs} +56 -3
  5. package/dist/{command-D8yNlaDo.cjs → command-CdXv1zNF.cjs} +3 -3
  6. package/dist/{command-BfIuJmeo.mjs → command-DRqrBuHM.mjs} +3 -3
  7. package/dist/{index-BByhFIIq.mjs → index-CriPm_z9.mjs} +15 -17
  8. package/dist/{index-BOqJ9hwi.cjs → index-LYPXVO_L.cjs} +98 -100
  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.d.cts +6 -0
  13. package/dist/lib.d.mts +6 -0
  14. package/dist/lib.mjs +1 -1
  15. package/dist/{persistence-CzpZpiL3.mjs → persistence-CqgPgbzN.mjs} +29 -7
  16. package/dist/{persistence-C33AMdtv.cjs → persistence-PzKU0QCa.cjs} +54 -32
  17. package/dist/{registerKillSessionHandler-BkzQulD9.cjs → registerKillSessionHandler-BDBPoQSA.cjs} +2 -2
  18. package/dist/{registerKillSessionHandler-BtSK7IOa.mjs → registerKillSessionHandler-C3M_-4Zg.mjs} +2 -2
  19. package/dist/{runClaude-C_WLfM6c.mjs → runClaude-D6Pdkevn.mjs} +250 -46
  20. package/dist/{runClaude-CNVufgZb.cjs → runClaude-IeRSC5qX.cjs} +270 -66
  21. package/dist/{runCodex-8eWjTPJr.mjs → runCodex-CsfUU1Wb.mjs} +216 -401
  22. package/dist/{runCodex-Dzy8anlX.cjs → runCodex-WRmgSK6L.cjs} +216 -401
  23. package/dist/{runGemini-nbr0mm-S.mjs → runGemini-CrH3dQ0Y.mjs} +5 -5
  24. package/dist/{runGemini-CgsVKP7m.cjs → runGemini-qBh6zs5G.cjs} +5 -5
  25. package/package.json +1 -2
  26. package/dist/future-Dq4Ha1Dn.cjs +0 -24
  27. 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-B8v4tczT.cjs');
5
- var persistence = require('./persistence-C33AMdtv.cjs');
6
- var index = require('./index-BOqJ9hwi.cjs');
7
- var BaseReasoningProcessor = require('./BaseReasoningProcessor-01KqA3Kz.cjs');
8
- var registerKillSessionHandler = require('./registerKillSessionHandler-BkzQulD9.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-D9dIR956.cjs');
5
+ var persistence = require('./persistence-PzKU0QCa.cjs');
6
+ var index = require('./index-LYPXVO_L.cjs');
7
+ var BaseReasoningProcessor = require('./BaseReasoningProcessor-_wxlqKB8.cjs');
8
+ var registerKillSessionHandler = require('./registerKillSessionHandler-BDBPoQSA.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";
@@ -899,6 +765,9 @@ function normalizeCodexBackendError(error) {
899
765
  }
900
766
  return text || "Codex backend exited unexpectedly";
901
767
  }
768
+ function getCodexLegacySwitchIgnoredMessage() {
769
+ return "Codex now runs in ACP-only mode. Staying in the current session.";
770
+ }
902
771
  function shouldEmitCodexReadyAfterWait(opts) {
903
772
  if (opts.readyAlreadySent) {
904
773
  return false;
@@ -925,58 +794,10 @@ function handleIncomingCodexMessageDuringRemoteTurn(opts) {
925
794
  }
926
795
  return total > 0 || interruptedTurn;
927
796
  }
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
797
  async function codexRemoteLauncher(session) {
976
798
  const messageBuffer = new registerKillSessionHandler.MessageBuffer();
977
799
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
978
800
  let inkInstance = null;
979
- let exitReason = null;
980
801
  let shouldExit = false;
981
802
  let abortController = new AbortController();
982
803
  let turnInFlight = false;
@@ -989,8 +810,10 @@ async function codexRemoteLauncher(session) {
989
810
  let accumulatedResponse = "";
990
811
  let isResponseInProgress = false;
991
812
  let taskStartedSent = false;
813
+ let shouldInjectHistoryOnNextSession = false;
992
814
  const permissionHandler = new CodexPermissionHandler(session.client);
993
815
  const selectionHandler = new CodexSelectionHandler(session.client);
816
+ const conversationHistory = new ConversationHistory({ maxMessages: 20, maxCharacters: 5e4 });
994
817
  const reasoningProcessor = new ReasoningProcessor((message) => {
995
818
  session.runtimeSession.sendCodexMessage(message);
996
819
  });
@@ -1043,10 +866,26 @@ async function codexRemoteLauncher(session) {
1043
866
  api.logger.debug("[Codex] Error disposing ACP backend:", error);
1044
867
  }
1045
868
  };
869
+ const emitStatusMessage = (message) => {
870
+ messageBuffer.addMessage(message, "status");
871
+ session.runtimeSession.sendSessionEvent({ type: "message", message });
872
+ };
873
+ const queueHistoryInjectionForRestart = (reason) => {
874
+ messageBuffer.addMessage("\u2550".repeat(40), "status");
875
+ if (conversationHistory.hasHistory()) {
876
+ shouldInjectHistoryOnNextSession = true;
877
+ const message = `${reason} Preserving ${conversationHistory.size()} earlier messages of context.`;
878
+ emitStatusMessage(message);
879
+ api.logger.debug(`[Codex] Will inject conversation history after restart: ${conversationHistory.getSummary()}`);
880
+ return;
881
+ }
882
+ emitStatusMessage(reason);
883
+ };
1046
884
  const emitFinalAssistantMessage = () => {
1047
885
  if (!accumulatedResponse.trim()) {
1048
886
  return;
1049
887
  }
888
+ conversationHistory.addAssistantMessage(accumulatedResponse);
1050
889
  session.runtimeSession.sendCodexMessage({
1051
890
  type: "message",
1052
891
  message: accumulatedResponse,
@@ -1261,11 +1100,9 @@ async function codexRemoteLauncher(session) {
1261
1100
  return result.backend;
1262
1101
  };
1263
1102
  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();
1103
+ const message = getCodexLegacySwitchIgnoredMessage();
1104
+ api.logger.debug("[Codex] Ignoring legacy switch request because Codex is ACP-only");
1105
+ emitStatusMessage(message);
1269
1106
  };
1270
1107
  session.setPendingInteractionSuperseder((reason = BaseReasoningProcessor.INTERACTION_SUPERSEDED_ERROR) => {
1271
1108
  return handleIncomingCodexMessageDuringRemoteTurn({
@@ -1326,17 +1163,9 @@ async function codexRemoteLauncher(session) {
1326
1163
  inkInstance = ink.render(React.createElement(CodexDisplay, {
1327
1164
  messageBuffer,
1328
1165
  logPath: process.env.DEBUG ? session.logPath : void 0,
1329
- title: "Remote Mode - Codex Messages",
1166
+ title: "Codex Agent Messages",
1330
1167
  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";
1168
+ api.logger.debug("[Codex] Exiting Codex ACP session from keyboard");
1340
1169
  shouldExit = true;
1341
1170
  await handleAbort();
1342
1171
  }
@@ -1376,8 +1205,7 @@ async function codexRemoteLauncher(session) {
1376
1205
  break;
1377
1206
  }
1378
1207
  if (wasSessionCreated && currentModeHash && message.hash !== currentModeHash) {
1379
- messageBuffer.addMessage("\u2550".repeat(40), "status");
1380
- messageBuffer.addMessage("Starting new Codex session (mode changed)...", "status");
1208
+ queueHistoryInjectionForRestart("Starting new Codex session (execution settings changed)...");
1381
1209
  await disposeBackend();
1382
1210
  session.clearSessionId();
1383
1211
  currentModeHash = null;
@@ -1404,27 +1232,33 @@ async function codexRemoteLauncher(session) {
1404
1232
  session.onSessionFound(sessionId);
1405
1233
  }
1406
1234
  session.onThinkingChange(true);
1407
- await activeBackend.sendPrompt(acpSessionId, message.message);
1235
+ let promptToSend = message.message;
1236
+ if (shouldInjectHistoryOnNextSession && conversationHistory.hasHistory()) {
1237
+ const historyContext = conversationHistory.getContextForNewSession();
1238
+ promptToSend = historyContext + promptToSend;
1239
+ api.logger.debug(`[Codex] Injected conversation history context (${historyContext.length} chars)`);
1240
+ }
1241
+ conversationHistory.addUserMessage(message.message);
1242
+ await activeBackend.sendPrompt(acpSessionId, promptToSend);
1408
1243
  await waitForResponseCompleteWithAbort(activeBackend, turnSignal);
1409
1244
  reasoningProcessor.completeCurrent();
1245
+ shouldInjectHistoryOnNextSession = false;
1410
1246
  } catch (error) {
1411
1247
  api.logger.warn("Error in codex ACP session:", error);
1412
1248
  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) {
1249
+ const isExpectedInterruption = isAbortError || turnSignal.aborted || shouldExit;
1250
+ if (isExpectedInterruption) {
1418
1251
  session.runtimeSession.sendCodexMessage({
1419
1252
  type: "turn_aborted",
1420
1253
  id: node_crypto.randomUUID()
1421
1254
  });
1422
- messageBuffer.addMessage("Aborted by user", "status");
1423
- session.runtimeSession.sendSessionEvent({ type: "message", message: "Aborted by user" });
1255
+ emitStatusMessage("Aborted by user");
1424
1256
  } else {
1425
1257
  const errorMessage = normalizeCodexBackendError(error);
1426
- messageBuffer.addMessage(errorMessage, "status");
1427
- session.runtimeSession.sendSessionEvent({ type: "message", message: errorMessage });
1258
+ emitStatusMessage(errorMessage);
1259
+ if (conversationHistory.hasHistory()) {
1260
+ shouldInjectHistoryOnNextSession = true;
1261
+ }
1428
1262
  await disposeBackend();
1429
1263
  session.clearSessionId();
1430
1264
  }
@@ -1478,33 +1312,16 @@ async function codexRemoteLauncher(session) {
1478
1312
  }
1479
1313
  messageBuffer.clear();
1480
1314
  }
1481
- api.logger.debug(`[Codex] ACP remote launcher returning: ${exitReason || "exit"}`);
1482
- return exitReason || "exit";
1315
+ api.logger.debug("[Codex] ACP remote launcher returning: exit");
1316
+ return "exit";
1483
1317
  }
1484
1318
 
1485
1319
  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
- }
1320
+ const displayMode = opts.startingMode ?? "local";
1321
+ await opts.session.onModeChange(displayMode);
1322
+ api.logger.debug(`[codex-loop] Starting ACP-only Codex launcher with display mode: ${displayMode}`);
1323
+ await codexRemoteLauncher(opts.session);
1324
+ return 0;
1508
1325
  }
1509
1326
 
1510
1327
  function supportsAgentStateUpdateEvents(sessionClient) {
@@ -1545,16 +1362,17 @@ async function syncControlledByUserState(sessionClient, controlledByUser) {
1545
1362
  });
1546
1363
  }
1547
1364
  function shouldSupersedeCodexPendingInteractions(opts) {
1548
- return (opts.currentMode ?? opts.startingMode) === "remote";
1365
+ return true;
1549
1366
  }
1550
1367
  async function runCodex(opts) {
1551
1368
  const sessionTag = node_crypto.randomUUID();
1552
1369
  api.connectionState.setBackend("Codex");
1553
- if (opts.startedBy === "daemon" && opts.startingMode === "local") {
1370
+ const requestedStartingMode = opts.startingMode ?? (opts.startedBy === "daemon" ? "remote" : "local");
1371
+ if (opts.startedBy === "daemon" && requestedStartingMode === "local") {
1554
1372
  throw new Error("Daemon-spawned Codex sessions cannot use local mode.");
1555
1373
  }
1556
1374
  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"}`);
1375
+ api.logger.debug(`[codex] Starting with options: startedBy=${opts.startedBy || "terminal"}, requestedStartingMode=${requestedStartingMode}, executionMode=acp-only`);
1558
1376
  const settings = await persistence.readSettings();
1559
1377
  const machineId = settings?.machineId;
1560
1378
  if (!machineId) {
@@ -1603,10 +1421,7 @@ async function runCodex(opts) {
1603
1421
  api.logger.debug("[START] Failed to report to daemon:", error);
1604
1422
  }
1605
1423
  }
1606
- const messageQueue = new registerKillSessionHandler.MessageQueue2((mode) => registerKillSessionHandler.hashObject({
1607
- permissionMode: mode.permissionMode,
1608
- model: mode.model
1609
- }));
1424
+ const messageQueue = new registerKillSessionHandler.MessageQueue2(getCodexExecutionFingerprint);
1610
1425
  let currentPermissionMode;
1611
1426
  let currentModel;
1612
1427
  sessionClient.onUserMessage((message) => {
@@ -1638,7 +1453,7 @@ async function runCodex(opts) {
1638
1453
  path: process.cwd(),
1639
1454
  logPath: api.logger.logFilePath,
1640
1455
  sessionId: null,
1641
- mode: opts.startingMode ?? "local",
1456
+ mode: requestedStartingMode,
1642
1457
  messageQueue,
1643
1458
  codexArgs: opts.codexArgs,
1644
1459
  onModeChange: async (mode) => {
@@ -1649,7 +1464,7 @@ async function runCodex(opts) {
1649
1464
  try {
1650
1465
  const exitCode = await codexLoop({
1651
1466
  session: codexSession,
1652
- startingMode: opts.startingMode ?? "local"
1467
+ startingMode: requestedStartingMode
1653
1468
  });
1654
1469
  sessionClient.sendSessionDeath();
1655
1470
  await sessionClient.flush();