happy-imou-cloud 2.1.48 → 2.1.50

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-CM3JbXUC.mjs → BaseReasoningProcessor-2qoX8OA1.mjs} +2 -2
  2. package/dist/{BaseReasoningProcessor-CKtDfihV.cjs → BaseReasoningProcessor-D3q5lh9h.cjs} +2 -2
  3. package/dist/{ProviderSelectionHandler-BHKIBw4t.cjs → ProviderSelectionHandler-CO5BYEgC.cjs} +2 -2
  4. package/dist/{ProviderSelectionHandler-vgZ2egD_.mjs → ProviderSelectionHandler-CO6foZET.mjs} +2 -2
  5. package/dist/{api-CSjP-Z3Y.mjs → api-6B4EMs47.mjs} +1 -1
  6. package/dist/{api-eaGKJjMt.cjs → api-D4JOaMll.cjs} +1 -1
  7. package/dist/{command-BzPI4N1n.mjs → command-BbJCdR2t.mjs} +2 -2
  8. package/dist/{command-DDoz1Eky.cjs → command-DuKDmbdM.cjs} +2 -2
  9. package/dist/{index-C8X1VlHZ.cjs → index-CZJH0CUT.cjs} +109 -25
  10. package/dist/{index-BzsBo3_Z.mjs → index-CyGHrYHl.mjs} +106 -22
  11. package/dist/index.cjs +2 -2
  12. package/dist/index.mjs +2 -2
  13. package/dist/lib.cjs +1 -1
  14. package/dist/lib.d.cts +2 -0
  15. package/dist/lib.d.mts +2 -0
  16. package/dist/lib.mjs +1 -1
  17. package/dist/{registerKillSessionHandler-CLCYBgZX.cjs → registerKillSessionHandler-Bo_MMbdY.cjs} +2 -2
  18. package/dist/{registerKillSessionHandler-BHu-3hZQ.mjs → registerKillSessionHandler-ggfGac_S.mjs} +2 -2
  19. package/dist/{runClaude-BkUbXE2F.cjs → runClaude-B4eoG3Pu.cjs} +4 -4
  20. package/dist/{runClaude-D3CBLW5o.mjs → runClaude-jDr3i0bO.mjs} +4 -4
  21. package/dist/{runCodex-DqzdgDwZ.mjs → runCodex-e_D1Jd_w.mjs} +267 -65
  22. package/dist/{runCodex-CdgrZK7P.cjs → runCodex-hDGeZAh5.cjs} +266 -64
  23. package/dist/{runGemini-BE05R24D.cjs → runGemini-CeA9aiD1.cjs} +4 -4
  24. package/dist/{runGemini-UZuiKe59.mjs → runGemini-Cur84FvO.mjs} +4 -4
  25. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
- import { m as initialMachineMetadata, R as RuntimeShell, f as formatDisplayMessage, n as resolveCanonicalToolNameV2, o as isTerminalReferenceOnlyPayload, t as truncateDisplayMessage } from './index-BzsBo3_Z.mjs';
2
- import { r as readSettings, H as HeadTailPreviewBuffer, e as HAPPY_ORG_REPLY_ACK_VERSION, f as HAPPY_ORG_TURN_REPORT_TAG, g as HAPPY_ORG_SUMMARY_MAX_LENGTH, j as HAPPY_ORG_REPEAT_THRESHOLD, l as logger, n as normalizePreviewableArtifactTarget } from './api-CSjP-Z3Y.mjs';
1
+ import { m as initialMachineMetadata, R as RuntimeShell, f as formatDisplayMessage, n as resolveCanonicalToolNameV2, o as isTerminalReferenceOnlyPayload, t as truncateDisplayMessage } from './index-CyGHrYHl.mjs';
2
+ import { r as readSettings, H as HeadTailPreviewBuffer, e as HAPPY_ORG_REPLY_ACK_VERSION, f as HAPPY_ORG_TURN_REPORT_TAG, g as HAPPY_ORG_SUMMARY_MAX_LENGTH, j as HAPPY_ORG_REPEAT_THRESHOLD, l as logger, n as normalizePreviewableArtifactTarget } from './api-6B4EMs47.mjs';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { basename } from 'node:path';
5
5
  import 'axios';
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  var node_crypto = require('node:crypto');
4
- var persistence = require('./api-eaGKJjMt.cjs');
4
+ var persistence = require('./api-D4JOaMll.cjs');
5
5
  require('cross-spawn');
6
6
  require('@agentclientprotocol/sdk');
7
- var index = require('./index-C8X1VlHZ.cjs');
7
+ var index = require('./index-CZJH0CUT.cjs');
8
8
  require('ps-list');
9
9
  require('fs');
10
10
  require('path');
@@ -25,9 +25,9 @@ require('tweetnacl');
25
25
  require('open');
26
26
  var React = require('react');
27
27
  var ink = require('ink');
28
- var ProviderSelectionHandler = require('./ProviderSelectionHandler-BHKIBw4t.cjs');
28
+ var ProviderSelectionHandler = require('./ProviderSelectionHandler-CO5BYEgC.cjs');
29
29
  var types = require('./types-DVk3crez.cjs');
30
- var registerKillSessionHandler = require('./registerKillSessionHandler-CLCYBgZX.cjs');
30
+ var registerKillSessionHandler = require('./registerKillSessionHandler-Bo_MMbdY.cjs');
31
31
  require('socket.io-client');
32
32
  require('expo-server-sdk');
33
33
  var node_util = require('node:util');
@@ -1,8 +1,8 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { l as logger, k as backoff, m as delay, d as AssistantMessageStream, o as AsyncLock, c as configuration, s as startOfflineReconnection, b as connectionState, A as ApiClient, i as isAuthenticationRequiredError, h as hashObject } from './api-CSjP-Z3Y.mjs';
2
+ import { l as logger, k as backoff, m as delay, d as AssistantMessageStream, o as AsyncLock, c as configuration, s as startOfflineReconnection, b as connectionState, A as ApiClient, i as isAuthenticationRequiredError, h as hashObject } from './api-6B4EMs47.mjs';
3
3
  import 'cross-spawn';
4
4
  import '@agentclientprotocol/sdk';
5
- import { q as getProjectPath, F as Future, u as claudeLocal, E as ExitCodeError, w as trimIdent, x as createClaudeBackend, f as formatDisplayMessage, t as truncateDisplayMessage, y as claudeCheckSession, z as projectPath, B as mapToClaudeMode, P as PushableAsyncIterable, C as query, D as AbortError, e as stopCaffeinate, p as publishSessionRegistration, H as getEnvironmentInfo, a as createSessionMetadata, I as startCaffeinate, b as closeProviderSession } from './index-BzsBo3_Z.mjs';
5
+ import { q as getProjectPath, F as Future, u as claudeLocal, E as ExitCodeError, w as trimIdent, x as createClaudeBackend, f as formatDisplayMessage, t as truncateDisplayMessage, y as claudeCheckSession, z as projectPath, B as mapToClaudeMode, P as PushableAsyncIterable, C as query, D as AbortError, e as stopCaffeinate, p as publishSessionRegistration, H as getEnvironmentInfo, a as createSessionMetadata, I as startCaffeinate, b as closeProviderSession } from './index-CyGHrYHl.mjs';
6
6
  import 'ps-list';
7
7
  import 'fs';
8
8
  import 'path';
@@ -23,9 +23,9 @@ import 'tweetnacl';
23
23
  import 'open';
24
24
  import React, { useState, useRef, useEffect, useCallback } from 'react';
25
25
  import { useStdout, useInput, Box, Text, render } from 'ink';
26
- import { c as createKeepAliveController, P as ProviderSelectionHandler, r as runModeLoop } from './ProviderSelectionHandler-vgZ2egD_.mjs';
26
+ import { c as createKeepAliveController, P as ProviderSelectionHandler, r as runModeLoop } from './ProviderSelectionHandler-CO6foZET.mjs';
27
27
  import { R as RawJSONLinesSchema } from './types-CiliQpqS.mjs';
28
- import { B as BasePermissionHandler, d as MessageBuffer, C as ConversationHistory$1, f as buildHappyOrgTurnPrompt, w as waitForResponseCompleteWithAbort, h as finalizeHappyOrgTurnWithBusinessAck, i as buildTurnResultPushNotification, t as createSessionTranscriptInkRenderer, j as buildReadyPushNotification, l as launchRuntimeHandleWithFactoryResult, n as renderTerminalOutputPreview, p as prepareTerminalOutputForForwarding, q as forwardAgentMessageToProviderSession, m as buildPermissionPushNotification, s as syncControlledByUserState, r as resolveHappyOrgQueuedTurn, e as ensureManagedProviderMachine, M as MissingMachineIdError, b as MessageQueue2, c as registerKillSessionHandler } from './registerKillSessionHandler-BHu-3hZQ.mjs';
28
+ import { B as BasePermissionHandler, d as MessageBuffer, C as ConversationHistory$1, f as buildHappyOrgTurnPrompt, w as waitForResponseCompleteWithAbort, h as finalizeHappyOrgTurnWithBusinessAck, i as buildTurnResultPushNotification, t as createSessionTranscriptInkRenderer, j as buildReadyPushNotification, l as launchRuntimeHandleWithFactoryResult, n as renderTerminalOutputPreview, p as prepareTerminalOutputForForwarding, q as forwardAgentMessageToProviderSession, m as buildPermissionPushNotification, s as syncControlledByUserState, r as resolveHappyOrgQueuedTurn, e as ensureManagedProviderMachine, M as MissingMachineIdError, b as MessageQueue2, c as registerKillSessionHandler } from './registerKillSessionHandler-ggfGac_S.mjs';
29
29
  import 'socket.io-client';
30
30
  import 'expo-server-sdk';
31
31
  import { isDeepStrictEqual } from 'node:util';
@@ -1,6 +1,6 @@
1
- import { p as preserveSessionRuntimeMetadata, l as logger, h as hashObject, d as AssistantMessageStream, b as connectionState, A as ApiClient } from './api-CSjP-Z3Y.mjs';
2
- import { B as BasePermissionHandler, d as MessageBuffer, C as ConversationHistory$1, f as buildHappyOrgTurnPrompt, w as waitForResponseCompleteWithAbort, h as finalizeHappyOrgTurnWithBusinessAck, i as buildTurnResultPushNotification, c as registerKillSessionHandler, j as buildReadyPushNotification, l as launchRuntimeHandleWithFactoryResult, t as createSessionTranscriptInkRenderer, k as extractPermissionRequestPushContext, m as buildPermissionPushNotification, n as renderTerminalOutputPreview, p as prepareTerminalOutputForForwarding, o as inferToolResultError, q as forwardAgentMessageToProviderSession, e as ensureManagedProviderMachine, M as MissingMachineIdError, b as MessageQueue2, r as resolveHappyOrgQueuedTurn, s as syncControlledByUserState } from './registerKillSessionHandler-BHu-3hZQ.mjs';
3
- import { f as formatDisplayMessage, v as validateCodexAcpSpawn, h as createCodexBackend, A as AcpBackend, t as truncateDisplayMessage, b as closeProviderSession, e as stopCaffeinate, i as resolveCodexExecutable, j as shouldUseShellForCodex, F as Future, k as readManagedSessionTag, l as resolveManagedSessionTag } from './index-BzsBo3_Z.mjs';
1
+ import { p as preserveSessionRuntimeMetadata, l as logger, h as hashObject, d as AssistantMessageStream, b as connectionState, A as ApiClient } from './api-6B4EMs47.mjs';
2
+ import { B as BasePermissionHandler, d as MessageBuffer, C as ConversationHistory$1, f as buildHappyOrgTurnPrompt, w as waitForResponseCompleteWithAbort, h as finalizeHappyOrgTurnWithBusinessAck, i as buildTurnResultPushNotification, c as registerKillSessionHandler, j as buildReadyPushNotification, l as launchRuntimeHandleWithFactoryResult, t as createSessionTranscriptInkRenderer, k as extractPermissionRequestPushContext, m as buildPermissionPushNotification, n as renderTerminalOutputPreview, p as prepareTerminalOutputForForwarding, o as inferToolResultError, q as forwardAgentMessageToProviderSession, e as ensureManagedProviderMachine, M as MissingMachineIdError, b as MessageQueue2, r as resolveHappyOrgQueuedTurn, s as syncControlledByUserState } from './registerKillSessionHandler-ggfGac_S.mjs';
3
+ import { f as formatDisplayMessage, v as validateCodexAcpSpawn, h as createCodexBackend, A as AcpBackend, t as truncateDisplayMessage, b as closeProviderSession, e as stopCaffeinate, i as resolveCodexExecutable, j as shouldUseShellForCodex, F as Future, k as readManagedSessionTag, l as resolveManagedSessionTag } from './index-CyGHrYHl.mjs';
4
4
  import 'cross-spawn';
5
5
  import '@agentclientprotocol/sdk';
6
6
  import { randomUUID } from 'node:crypto';
@@ -10,7 +10,7 @@ import 'path';
10
10
  import 'os';
11
11
  import 'child_process';
12
12
  import fs from 'node:fs';
13
- import { join, basename, normalize, delimiter } from 'node:path';
13
+ import { join, normalize, basename, delimiter } from 'node:path';
14
14
  import os from 'node:os';
15
15
  import { spawn, execFileSync } from 'node:child_process';
16
16
  import 'node:readline';
@@ -24,8 +24,8 @@ import 'tweetnacl';
24
24
  import 'open';
25
25
  import React, { useState, useRef, useEffect, useCallback } from 'react';
26
26
  import { useStdout, useInput, Box, Text, render } from 'ink';
27
- import { c as createKeepAliveController, P as ProviderSelectionHandler, r as runModeLoop } from './ProviderSelectionHandler-vgZ2egD_.mjs';
28
- import { B as BaseReasoningProcessor, b as bootstrapManagedProviderSession } from './BaseReasoningProcessor-CM3JbXUC.mjs';
27
+ import { c as createKeepAliveController, P as ProviderSelectionHandler, r as runModeLoop } from './ProviderSelectionHandler-CO6foZET.mjs';
28
+ import { B as BaseReasoningProcessor, b as bootstrapManagedProviderSession } from './BaseReasoningProcessor-2qoX8OA1.mjs';
29
29
  import 'zod';
30
30
  import 'socket.io-client';
31
31
  import 'expo-server-sdk';
@@ -238,12 +238,19 @@ class CodexSession {
238
238
  this.sessionId = sessionId;
239
239
  this.client.updateMetadata((metadata) => ({
240
240
  ...metadata,
241
- codexSessionId: sessionId
241
+ codexSessionId: sessionId,
242
+ codexSessionCwd: this.path
242
243
  }));
243
244
  logger.debug(`[CodexSession] Session ID ${sessionId} added to metadata`);
244
245
  };
245
246
  clearSessionId = () => {
246
247
  this.sessionId = null;
248
+ this.client.updateMetadata((metadata) => {
249
+ const nextMetadata = { ...metadata };
250
+ delete nextMetadata.codexSessionId;
251
+ delete nextMetadata.codexSessionCwd;
252
+ return nextMetadata;
253
+ });
247
254
  logger.debug("[CodexSession] Session ID cleared");
248
255
  };
249
256
  handleUserObserverEphemeral = (event) => {
@@ -515,6 +522,113 @@ function getCodexExecutionFingerprint(mode) {
515
522
  });
516
523
  }
517
524
 
525
+ function getCodexSessionsRoot(options = {}) {
526
+ const codexHomeDir = options.codexHomeDir || process.env.CODEX_HOME || join(os.homedir(), ".codex");
527
+ return join(codexHomeDir, "sessions");
528
+ }
529
+ function collectFilesRecursive(dir, acc = []) {
530
+ let entries;
531
+ try {
532
+ entries = fs.readdirSync(dir, { withFileTypes: true });
533
+ } catch {
534
+ return acc;
535
+ }
536
+ for (const entry of entries) {
537
+ const full = join(dir, entry.name);
538
+ if (entry.isDirectory()) {
539
+ collectFilesRecursive(full, acc);
540
+ } else if (entry.isFile()) {
541
+ acc.push(full);
542
+ }
543
+ }
544
+ return acc;
545
+ }
546
+ function extractCodexSessionIdFromPath(filePath) {
547
+ 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$/);
548
+ return match?.[1] ?? null;
549
+ }
550
+ function normalizePathForSessionMatch(path) {
551
+ const normalizedPath = normalize(path).replace(/[\\/]+/g, "/").replace(/\/+$/, "");
552
+ return process.platform === "win32" ? normalizedPath.toLowerCase() : normalizedPath;
553
+ }
554
+ function extractCodexSessionCwdFromFile(filePath) {
555
+ let contents;
556
+ try {
557
+ contents = fs.readFileSync(filePath, "utf8");
558
+ } catch {
559
+ return null;
560
+ }
561
+ for (const line of contents.split(/\r?\n/)) {
562
+ if (!line.trim()) {
563
+ continue;
564
+ }
565
+ let record;
566
+ try {
567
+ record = JSON.parse(line);
568
+ } catch {
569
+ continue;
570
+ }
571
+ if (record.type !== "session_meta" || typeof record.payload !== "object" || record.payload === null) {
572
+ continue;
573
+ }
574
+ const cwd = record.payload.cwd;
575
+ return typeof cwd === "string" && cwd.trim().length > 0 ? cwd : null;
576
+ }
577
+ return null;
578
+ }
579
+ function codexSessionFileMatchesCwd(filePath, cwd) {
580
+ const expectedCwd = cwd?.trim();
581
+ if (!expectedCwd) {
582
+ return true;
583
+ }
584
+ const actualCwd = extractCodexSessionCwdFromFile(filePath);
585
+ if (!actualCwd) {
586
+ return false;
587
+ }
588
+ return normalizePathForSessionMatch(actualCwd) === normalizePathForSessionMatch(expectedCwd);
589
+ }
590
+ function findCodexResumeFile(sessionId, options = {}) {
591
+ if (!sessionId) return null;
592
+ try {
593
+ const candidates = collectFilesRecursive(getCodexSessionsRoot(options)).filter((full) => full.endsWith(`-${sessionId}.jsonl`)).filter((full) => {
594
+ try {
595
+ return fs.statSync(full).isFile();
596
+ } catch {
597
+ return false;
598
+ }
599
+ }).filter((full) => codexSessionFileMatchesCwd(full, options.cwd)).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
600
+ return candidates[0] || null;
601
+ } catch {
602
+ return null;
603
+ }
604
+ }
605
+ function isCodexResumeSessionValidForCwd(sessionId, cwd, options = {}) {
606
+ if (!sessionId?.trim()) {
607
+ return false;
608
+ }
609
+ return findCodexResumeFile(sessionId, { ...options, cwd }) !== null;
610
+ }
611
+ function findLatestCodexSessionIdSince(startTimeMs, options = {}) {
612
+ try {
613
+ const candidates = collectFilesRecursive(getCodexSessionsRoot(options)).filter((full) => full.endsWith(".jsonl")).filter((full) => {
614
+ try {
615
+ return fs.statSync(full).mtimeMs >= startTimeMs;
616
+ } catch {
617
+ return false;
618
+ }
619
+ }).filter((full) => codexSessionFileMatchesCwd(full, options.cwd)).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
620
+ for (const candidate of candidates) {
621
+ const sessionId = extractCodexSessionIdFromPath(candidate);
622
+ if (sessionId) {
623
+ return sessionId;
624
+ }
625
+ }
626
+ } catch {
627
+ return null;
628
+ }
629
+ return null;
630
+ }
631
+
518
632
  function delay(ms) {
519
633
  return new Promise((resolve) => setTimeout(resolve, ms));
520
634
  }
@@ -690,6 +804,17 @@ function shouldInjectLargeOutputRecoveryHint(error) {
690
804
  ].filter(Boolean).join("\n").toLowerCase();
691
805
  return searchableText.includes("memory allocation of") || searchableText.includes("out of memory") || searchableText.includes("oversized command output");
692
806
  }
807
+ function isCodexUnknownResumeSessionError(error) {
808
+ const record = typeof error === "object" && error !== null ? error : null;
809
+ const searchableText = [
810
+ formatDisplayMessage(error).trim(),
811
+ record ? formatDisplayMessage(record.message).trim() : "",
812
+ record ? formatDisplayMessage(record.stderr).trim() : "",
813
+ record ? formatDisplayMessage(record.detail).trim() : "",
814
+ record ? formatDisplayMessage(record.data).trim() : ""
815
+ ].filter(Boolean).join("\n");
816
+ return /unknown (session|thread)|session .* not found|thread .* not found|conversation .* not found|missing rollout path for thread|state db missing rollout path|no rollout found for thread id|resource not found/i.test(searchableText);
817
+ }
693
818
  function normalizeCodexBackendError(error) {
694
819
  const record = typeof error === "object" && error !== null ? error : null;
695
820
  const text = formatDisplayMessage(error).trim();
@@ -1173,6 +1298,11 @@ async function codexRemoteLauncher(session) {
1173
1298
  };
1174
1299
  const createRuntimeHandle = async (mode) => {
1175
1300
  const executionMode = resolveCodexAcpExecutionMode(mode);
1301
+ const resumeSessionId = session.sessionId && isCodexResumeSessionValidForCwd(session.sessionId, session.path) ? session.sessionId : null;
1302
+ if (session.sessionId && !resumeSessionId) {
1303
+ logger.debug(`[Codex] Refusing to resume Codex session ${session.sessionId} because its local session file does not match cwd ${session.path}`);
1304
+ session.clearSessionId();
1305
+ }
1176
1306
  const validation = validateCodexAcpSpawn({
1177
1307
  command: process.env.HAPPY_CODEX_ACP_COMMAND,
1178
1308
  model: executionMode.model,
@@ -1193,7 +1323,7 @@ async function codexRemoteLauncher(session) {
1193
1323
  sandbox: executionMode.sandbox,
1194
1324
  approvalPolicy: executionMode.approvalPolicy,
1195
1325
  permissionHandler,
1196
- resumeSessionId: session.sessionId,
1326
+ resumeSessionId,
1197
1327
  selectionHandler: {
1198
1328
  handleSelection: (request) => selectionHandler.requestSelection(request)
1199
1329
  }
@@ -1344,12 +1474,31 @@ async function codexRemoteLauncher(session) {
1344
1474
  const turnSignal = abortController.signal;
1345
1475
  let sentTurnResultPush = false;
1346
1476
  let turnStatus = "task_complete";
1477
+ let retryingWithFreshSession = false;
1347
1478
  currentHappyOrgTurn = message.mode.happyOrg ?? null;
1348
1479
  try {
1349
1480
  turnInFlight = true;
1350
1481
  shouldCommitAccumulatedResponse = false;
1351
1482
  const isNewRuntimeSession = runtimeHandle === null;
1352
- const activeRuntimeHandle = runtimeHandle ?? await createRuntimeHandle(message.mode);
1483
+ const attemptedResumeSessionId = isNewRuntimeSession ? session.sessionId : null;
1484
+ let activeRuntimeHandle;
1485
+ try {
1486
+ activeRuntimeHandle = runtimeHandle ?? await createRuntimeHandle(message.mode);
1487
+ } catch (error) {
1488
+ if (!isNewRuntimeSession || !attemptedResumeSessionId || !isCodexUnknownResumeSessionError(error) || turnSignal.aborted) {
1489
+ throw error;
1490
+ }
1491
+ logger.debug(`[Codex] Resume session ${attemptedResumeSessionId} failed during ACP startup; retrying with a fresh session`, error);
1492
+ queueHistoryInjectionForRestart("Codex resume session is unavailable. Retrying with a fresh Codex session...");
1493
+ session.clearSessionId();
1494
+ currentModeHash = null;
1495
+ permissionHandler.reset();
1496
+ selectionHandler.reset();
1497
+ resetTurnState();
1498
+ pending = message;
1499
+ retryingWithFreshSession = true;
1500
+ continue;
1501
+ }
1353
1502
  if (!activeRuntimeHandle) {
1354
1503
  throw new Error("Failed to create Codex ACP backend");
1355
1504
  }
@@ -1376,8 +1525,25 @@ ${message.message}` : message.message;
1376
1525
  promptToSend = buildHappyOrgTurnPrompt(promptToSend, message.mode.happyOrg);
1377
1526
  }
1378
1527
  conversationHistory.addUserMessage(message.message);
1379
- await activeRuntimeHandle.sendPrompt(promptToSend);
1380
- await waitForResponseCompleteWithAbort(activeRuntimeHandle.backend, turnSignal);
1528
+ try {
1529
+ await activeRuntimeHandle.sendPrompt(promptToSend);
1530
+ await waitForResponseCompleteWithAbort(activeRuntimeHandle.backend, turnSignal);
1531
+ } catch (error) {
1532
+ if (!isNewRuntimeSession || !attemptedResumeSessionId || !isCodexUnknownResumeSessionError(error) || turnSignal.aborted) {
1533
+ throw error;
1534
+ }
1535
+ logger.debug(`[Codex] Resume session ${attemptedResumeSessionId} is unavailable; retrying with a fresh ACP session`, error);
1536
+ queueHistoryInjectionForRestart("Codex resume session is unavailable. Retrying with a fresh Codex session...");
1537
+ await disposeRuntimeHandle();
1538
+ session.clearSessionId();
1539
+ currentModeHash = null;
1540
+ permissionHandler.reset();
1541
+ selectionHandler.reset();
1542
+ resetTurnState();
1543
+ pending = message;
1544
+ retryingWithFreshSession = true;
1545
+ continue;
1546
+ }
1381
1547
  reasoningProcessor.completeCurrent();
1382
1548
  shouldCommitAccumulatedResponse = true;
1383
1549
  shouldInjectHistoryOnNextSession = false;
@@ -1407,6 +1573,9 @@ ${message.message}` : message.message;
1407
1573
  }
1408
1574
  } finally {
1409
1575
  turnInFlight = false;
1576
+ if (retryingWithFreshSession) {
1577
+ continue;
1578
+ }
1410
1579
  const finalizedTurn = await finalizeHappyOrgTurnWithBusinessAck({
1411
1580
  metadata: session.runtimeSession.getMetadataSnapshot?.() ?? null,
1412
1581
  queuedTurn: message.mode.happyOrg,
@@ -1502,52 +1671,6 @@ ${message.message}` : message.message;
1502
1671
  return shouldSwitchToLocal ? "switch" : "exit";
1503
1672
  }
1504
1673
 
1505
- function getCodexSessionsRoot(options = {}) {
1506
- const codexHomeDir = options.codexHomeDir || process.env.CODEX_HOME || join(os.homedir(), ".codex");
1507
- return join(codexHomeDir, "sessions");
1508
- }
1509
- function collectFilesRecursive(dir, acc = []) {
1510
- let entries;
1511
- try {
1512
- entries = fs.readdirSync(dir, { withFileTypes: true });
1513
- } catch {
1514
- return acc;
1515
- }
1516
- for (const entry of entries) {
1517
- const full = join(dir, entry.name);
1518
- if (entry.isDirectory()) {
1519
- collectFilesRecursive(full, acc);
1520
- } else if (entry.isFile()) {
1521
- acc.push(full);
1522
- }
1523
- }
1524
- return acc;
1525
- }
1526
- function extractCodexSessionIdFromPath(filePath) {
1527
- 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$/);
1528
- return match?.[1] ?? null;
1529
- }
1530
- function findLatestCodexSessionIdSince(startTimeMs, options = {}) {
1531
- try {
1532
- const candidates = collectFilesRecursive(getCodexSessionsRoot(options)).filter((full) => full.endsWith(".jsonl")).filter((full) => {
1533
- try {
1534
- return fs.statSync(full).mtimeMs >= startTimeMs;
1535
- } catch {
1536
- return false;
1537
- }
1538
- }).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
1539
- for (const candidate of candidates) {
1540
- const sessionId = extractCodexSessionIdFromPath(candidate);
1541
- if (sessionId) {
1542
- return sessionId;
1543
- }
1544
- }
1545
- } catch {
1546
- return null;
1547
- }
1548
- return null;
1549
- }
1550
-
1551
1674
  const MANAGED_CODEX_HOME_PREFIX = "happy-codex-home-";
1552
1675
  function isManagedCodexHomePath(value) {
1553
1676
  const trimmed = value?.trim();
@@ -1614,12 +1737,17 @@ function terminateCodexLocalChild(pid) {
1614
1737
  }
1615
1738
  async function codexLocal(opts) {
1616
1739
  const baseArgs = [...opts.codexArgs ?? []];
1617
- const args = opts.sessionId ? ["resume", opts.sessionId, ...baseArgs] : baseArgs;
1618
- const startTime = Date.now();
1619
- let detectedSessionId = opts.sessionId;
1620
- const codexExecutable = resolveCodexExecutable();
1621
1740
  const codexEnv = buildCodexLocalEnv(process.env);
1622
1741
  const codexHomeDir = resolveCodexLocalHomeDir(codexEnv);
1742
+ const resumeSessionId = opts.sessionId && isCodexResumeSessionValidForCwd(opts.sessionId, opts.path, { codexHomeDir }) ? opts.sessionId : null;
1743
+ if (opts.sessionId && !resumeSessionId) {
1744
+ logger.debug(`[CodexLocal] Refusing to resume Codex session ${opts.sessionId} because its local session file does not match cwd ${opts.path}`);
1745
+ opts.onResumeSessionInvalid?.(opts.sessionId);
1746
+ }
1747
+ const args = resumeSessionId ? ["resume", resumeSessionId, ...baseArgs] : baseArgs;
1748
+ const startTime = Date.now();
1749
+ let detectedSessionId = resumeSessionId;
1750
+ const codexExecutable = resolveCodexExecutable();
1623
1751
  logger.debug(`[CodexLocal] Spawning ${codexExecutable} with args: ${JSON.stringify(args)}`);
1624
1752
  releaseTerminalInputForChild();
1625
1753
  clearTerminalForChild();
@@ -1645,7 +1773,7 @@ async function codexLocal(opts) {
1645
1773
  if (detectedSessionId) {
1646
1774
  return;
1647
1775
  }
1648
- const nextSessionId = findLatestCodexSessionIdSince(startTime, { codexHomeDir });
1776
+ const nextSessionId = findLatestCodexSessionIdSince(startTime, { codexHomeDir, cwd: opts.path });
1649
1777
  if (nextSessionId) {
1650
1778
  detectedSessionId = nextSessionId;
1651
1779
  logger.debug(`[CodexLocal] Detected session ID: ${nextSessionId}`);
@@ -1788,6 +1916,7 @@ async function codexLocalLauncher(session) {
1788
1916
  abort: processAbortController.signal,
1789
1917
  codexArgs: session.codexArgs,
1790
1918
  onSessionFound: recordSessionFound,
1919
+ onResumeSessionInvalid: session.clearSessionId,
1791
1920
  onThinkingChange: session.onThinkingChange
1792
1921
  });
1793
1922
  if (sessionId) {
@@ -1913,6 +2042,61 @@ function installFatalProcessHandlers(options) {
1913
2042
  };
1914
2043
  }
1915
2044
 
2045
+ function normalizeResumeSessionId(value) {
2046
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
2047
+ }
2048
+ function normalizeCodexArgsForHappy(options) {
2049
+ const sourceArgs = options.codexArgs ?? [];
2050
+ const codexArgs = [];
2051
+ let resumeSessionId = normalizeResumeSessionId(options.resumeSessionId);
2052
+ for (let index = 0; index < sourceArgs.length; index += 1) {
2053
+ const arg = sourceArgs[index];
2054
+ if (arg === "--resume") {
2055
+ const nextArg = sourceArgs[index + 1];
2056
+ const normalizedNextArg = normalizeResumeSessionId(nextArg);
2057
+ if (!normalizedNextArg || nextArg?.startsWith("-")) {
2058
+ throw new Error("Codex --resume requires a provider session id when used through Happy.");
2059
+ }
2060
+ resumeSessionId ??= normalizedNextArg;
2061
+ index += 1;
2062
+ continue;
2063
+ }
2064
+ if (arg.startsWith("--resume=")) {
2065
+ const normalizedValue = normalizeResumeSessionId(arg.slice("--resume=".length));
2066
+ if (!normalizedValue) {
2067
+ throw new Error("Codex --resume requires a provider session id when used through Happy.");
2068
+ }
2069
+ resumeSessionId ??= normalizedValue;
2070
+ continue;
2071
+ }
2072
+ if (index === 0 && arg === "resume") {
2073
+ const nextArg = sourceArgs[index + 1];
2074
+ const normalizedNextArg = normalizeResumeSessionId(nextArg);
2075
+ if (normalizedNextArg && !nextArg?.startsWith("-")) {
2076
+ resumeSessionId ??= normalizedNextArg;
2077
+ index += 1;
2078
+ continue;
2079
+ }
2080
+ }
2081
+ codexArgs.push(arg);
2082
+ }
2083
+ return {
2084
+ codexArgs,
2085
+ resumeSessionId
2086
+ };
2087
+ }
2088
+
2089
+ function normalizePathForCodexResumeIdentity(path) {
2090
+ const normalized = path.trim().replace(/[\\/]+/g, "/").replace(/\/+$/, "");
2091
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
2092
+ }
2093
+ function codexResumeCwdMatches(expected, actual) {
2094
+ const normalizedExpected = expected?.trim();
2095
+ if (!normalizedExpected) {
2096
+ return true;
2097
+ }
2098
+ return normalizePathForCodexResumeIdentity(normalizedExpected) === normalizePathForCodexResumeIdentity(actual);
2099
+ }
1916
2100
  function resolveInitialCodexPermissionMode(opts) {
1917
2101
  if (opts.permissionMode) {
1918
2102
  return opts.permissionMode;
@@ -1948,6 +2132,10 @@ function resolveIncomingCodexPermissionMode(opts) {
1948
2132
  };
1949
2133
  }
1950
2134
  async function runCodex(opts) {
2135
+ const normalizedArgs = normalizeCodexArgsForHappy({
2136
+ codexArgs: opts.codexArgs,
2137
+ resumeSessionId: opts.resumeSessionId
2138
+ });
1951
2139
  const managedSessionTag = opts.startedBy === "daemon" ? readManagedSessionTag() : null;
1952
2140
  const sessionTag = managedSessionTag ?? resolveManagedSessionTag(
1953
2141
  opts.startedBy === "daemon" ? process.env : {}
@@ -1958,7 +2146,7 @@ async function runCodex(opts) {
1958
2146
  permissionMode: opts.permissionMode,
1959
2147
  startedBy: opts.startedBy,
1960
2148
  startingMode: requestedStartingMode,
1961
- codexArgs: opts.codexArgs
2149
+ codexArgs: normalizedArgs.codexArgs
1962
2150
  });
1963
2151
  const preserveCurrentPermissionModeForRemoteDefault = opts.startedBy === "daemon" && requestedStartingMode === "remote";
1964
2152
  if (opts.startedBy === "daemon" && requestedStartingMode === "local") {
@@ -2008,6 +2196,20 @@ async function runCodex(opts) {
2008
2196
  }
2009
2197
  });
2010
2198
  sessionClient = initialSession;
2199
+ const metadataCodexSessionCwd = typeof metadata.codexSessionCwd === "string" ? metadata.codexSessionCwd : null;
2200
+ const requestedResumeSessionId = normalizedArgs.resumeSessionId;
2201
+ const requestedResumeCwd = opts.resumeSessionCwd ?? metadataCodexSessionCwd;
2202
+ const resumeSessionMatchesCwd = requestedResumeSessionId ? codexResumeCwdMatches(requestedResumeCwd, metadata.path) && isCodexResumeSessionValidForCwd(requestedResumeSessionId, metadata.path) : false;
2203
+ const initialCodexSessionId = requestedResumeSessionId && resumeSessionMatchesCwd ? requestedResumeSessionId : null;
2204
+ if (requestedResumeSessionId && !resumeSessionMatchesCwd) {
2205
+ logger.debug(`[codex] Ignoring resume session ${requestedResumeSessionId} because it does not match cwd ${metadata.path}`);
2206
+ sessionClient.updateMetadata((currentMetadata) => {
2207
+ const nextMetadata = { ...currentMetadata };
2208
+ delete nextMetadata.codexSessionId;
2209
+ delete nextMetadata.codexSessionCwd;
2210
+ return nextMetadata;
2211
+ });
2212
+ }
2011
2213
  const messageQueue = new MessageQueue2(getCodexExecutionFingerprint);
2012
2214
  let currentPermissionMode = initialPermissionMode;
2013
2215
  let currentModel;
@@ -2054,10 +2256,10 @@ async function runCodex(opts) {
2054
2256
  path: metadata.path,
2055
2257
  logPath: logger.logFilePath,
2056
2258
  startupRolePrompt: happyOrgStartupBinding?.identityPrompt ?? null,
2057
- sessionId: null,
2259
+ sessionId: initialCodexSessionId,
2058
2260
  mode: requestedStartingMode,
2059
2261
  messageQueue,
2060
- codexArgs: opts.codexArgs,
2262
+ codexArgs: normalizedArgs.codexArgs,
2061
2263
  protocolDescriptor,
2062
2264
  protocolStateSources: userScopedObserver ? [userScopedObserver] : [],
2063
2265
  userObserver: userScopedObserver,