happy-imou-cloud 2.1.49 → 2.1.51

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 (55) hide show
  1. package/dist/AcpBackend-CqO3D07V.mjs +2619 -0
  2. package/dist/AcpBackend-XPiTd6ph.cjs +2621 -0
  3. package/dist/{BaseReasoningProcessor-Dn9NcoHz.cjs → BaseReasoningProcessor-BD9tiwep.cjs} +1 -144
  4. package/dist/{BaseReasoningProcessor-CAVeOdyo.mjs → BaseReasoningProcessor-CjlayL2f.mjs} +2 -144
  5. package/dist/ConversationHistory-Bl2doTA-.cjs +780 -0
  6. package/dist/ConversationHistory-CI5bBfuA.mjs +771 -0
  7. package/dist/{ProviderSelectionHandler-BJJc7qOR.cjs → ProviderSelectionHandler-C7GE5QjX.cjs} +6 -6
  8. package/dist/{ProviderSelectionHandler-DIYidT13.mjs → ProviderSelectionHandler-uQ8jzdzr.mjs} +2 -2
  9. package/dist/RuntimeShell-BDt42io_.mjs +252 -0
  10. package/dist/RuntimeShell-D_Te12wq.cjs +258 -0
  11. package/dist/bootstrapManagedProviderSession-Bln-TwyB.cjs +147 -0
  12. package/dist/bootstrapManagedProviderSession-D2Z6YU3n.mjs +145 -0
  13. package/dist/claude-BKNT-2fG.cjs +1080 -0
  14. package/dist/claude-CnN5WCWj.mjs +1073 -0
  15. package/dist/codex-DLGP8WF6.mjs +577 -0
  16. package/dist/codex-Fv2eali8.cjs +582 -0
  17. package/dist/{command-VcH4hbhi.cjs → command-BWPlJyCN.cjs} +16 -8
  18. package/dist/{command-CzfRRhVe.mjs → command-CELwsYoG.mjs} +15 -7
  19. package/dist/config-CFL0Gkqt.cjs +184 -0
  20. package/dist/config-ChSPe7p9.mjs +174 -0
  21. package/dist/createDefaultRuntimeShell-BXu3vCvT.cjs +33 -0
  22. package/dist/createDefaultRuntimeShell-DOg6g3-G.mjs +31 -0
  23. package/dist/cursor-Blq1cHdr.cjs +91 -0
  24. package/dist/cursor-CwPNSy_A.mjs +88 -0
  25. package/dist/future-Dq4Ha1Dn.cjs +24 -0
  26. package/dist/future-xRdLl3vf.mjs +22 -0
  27. package/dist/{index-xa1kwZoj.cjs → index-B_JYgMUS.cjs} +189 -5352
  28. package/dist/{index-7Z93BoVn.mjs → index-CX-F_fuk.mjs} +177 -5331
  29. package/dist/index.cjs +2 -2
  30. package/dist/index.mjs +2 -2
  31. package/dist/installFatalProcessHandlers-0vaw9MAz.mjs +55 -0
  32. package/dist/installFatalProcessHandlers-CyURn5Bp.cjs +57 -0
  33. package/dist/launch-BoCCEd5p.mjs +63 -0
  34. package/dist/launch-wZA5BcvS.cjs +66 -0
  35. package/dist/lib.cjs +2 -3
  36. package/dist/lib.d.cts +20 -17
  37. package/dist/lib.d.mts +20 -17
  38. package/dist/lib.mjs +1 -2
  39. package/dist/resolveCommand-B3BGyBE2.mjs +189 -0
  40. package/dist/resolveCommand-DYMd9PNC.cjs +193 -0
  41. package/dist/{runClaude-zCwRhpOw.mjs → runClaude-Be0myF9k.mjs} +8 -5
  42. package/dist/{runClaude-BBGNmGj6.cjs → runClaude-DZJt5er7.cjs} +46 -43
  43. package/dist/{runCodex-BbgLVjb9.mjs → runCodex-BSnyN4m7.mjs} +226 -117
  44. package/dist/{runCodex-jUU6U2tZ.cjs → runCodex-DTCcGRue.cjs} +269 -160
  45. package/dist/runCursor-Bn1PuwJy.cjs +506 -0
  46. package/dist/runCursor-M6dQ6bGF.mjs +504 -0
  47. package/dist/{runGemini-DcwNsudA.mjs → runGemini-BNm4vYKA.mjs} +279 -5
  48. package/dist/{runGemini-C0NT8MHK.cjs → runGemini-Bn3lFhz6.cjs} +309 -35
  49. package/dist/{registerKillSessionHandler-DLDg2EES.mjs → sessionControl-1bT_7OI6.mjs} +1643 -2405
  50. package/dist/{registerKillSessionHandler-CfCya6si.cjs → sessionControl-flKnQrx0.cjs} +1647 -2417
  51. package/dist/{api-DnqaNvyV.mjs → types-B5vtxa38.mjs} +55 -5
  52. package/dist/{api-D7nAeZi7.cjs → types-CttABk32.cjs} +55 -4
  53. package/package.json +2 -2
  54. package/dist/types-CiliQpqS.mjs +0 -52
  55. package/dist/types-DVk3crez.cjs +0 -54
@@ -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-DnqaNvyV.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-DLDg2EES.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-7Z93BoVn.mjs';
1
+ import { m as preserveSessionRuntimeMetadata, l as logger, h as hashObject, d as AssistantMessageStream, b as connectionState, A as ApiClient } from './types-B5vtxa38.mjs';
2
+ import { B as BasePermissionHandler, d as buildHappyOrgTurnPrompt, w as waitForResponseCompleteWithAbort, f as finalizeHappyOrgTurnWithBusinessAck, r as registerKillSessionHandler, i as renderTerminalOutputPreview, p as prepareTerminalOutputForForwarding, j as inferToolResultError, h as forwardAgentMessageToProviderSession, e as ensureManagedProviderMachine, M as MissingMachineIdError, b as MessageQueue2, c as resolveHappyOrgQueuedTurn, s as syncControlledByUserState } from './sessionControl-1bT_7OI6.mjs';
3
+ import { b as closeProviderSession, s as stopCaffeinate, r as readManagedSessionTag, a as resolveManagedSessionTag } from './index-CX-F_fuk.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,12 +24,19 @@ 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-DIYidT13.mjs';
28
- import { B as BaseReasoningProcessor, b as bootstrapManagedProviderSession } from './BaseReasoningProcessor-CAVeOdyo.mjs';
29
- import 'zod';
27
+ import { c as createKeepAliveController, P as ProviderSelectionHandler, r as runModeLoop } from './ProviderSelectionHandler-uQ8jzdzr.mjs';
30
28
  import 'socket.io-client';
31
29
  import 'expo-server-sdk';
32
- import './types-CiliQpqS.mjs';
30
+ import { M as MessageBuffer, C as ConversationHistory$1, b as buildTurnResultPushNotification, a as buildReadyPushNotification, l as launchRuntimeHandleWithFactoryResult, d as createSessionTranscriptInkRenderer, e as extractPermissionRequestPushContext, c as buildPermissionPushNotification } from './ConversationHistory-CI5bBfuA.mjs';
31
+ import { f as formatDisplayMessage, t as truncateDisplayMessage } from './RuntimeShell-BDt42io_.mjs';
32
+ import { A as AcpBackend } from './AcpBackend-CqO3D07V.mjs';
33
+ import { v as validateCodexAcpSpawn } from './resolveCommand-B3BGyBE2.mjs';
34
+ import { c as createCodexBackend, a as resolveCodexExecutable, s as shouldUseShellForCodex } from './codex-DLGP8WF6.mjs';
35
+ import { B as BaseReasoningProcessor } from './BaseReasoningProcessor-CjlayL2f.mjs';
36
+ import { F as Future } from './future-xRdLl3vf.mjs';
37
+ import { b as bootstrapManagedProviderSession } from './bootstrapManagedProviderSession-D2Z6YU3n.mjs';
38
+ import { i as installFatalProcessHandlers } from './installFatalProcessHandlers-0vaw9MAz.mjs';
39
+ import 'zod';
33
40
  import 'qrcode-terminal';
34
41
  import 'node:module';
35
42
  import 'url';
@@ -238,12 +245,19 @@ class CodexSession {
238
245
  this.sessionId = sessionId;
239
246
  this.client.updateMetadata((metadata) => ({
240
247
  ...metadata,
241
- codexSessionId: sessionId
248
+ codexSessionId: sessionId,
249
+ codexSessionCwd: this.path
242
250
  }));
243
251
  logger.debug(`[CodexSession] Session ID ${sessionId} added to metadata`);
244
252
  };
245
253
  clearSessionId = () => {
246
254
  this.sessionId = null;
255
+ this.client.updateMetadata((metadata) => {
256
+ const nextMetadata = { ...metadata };
257
+ delete nextMetadata.codexSessionId;
258
+ delete nextMetadata.codexSessionCwd;
259
+ return nextMetadata;
260
+ });
247
261
  logger.debug("[CodexSession] Session ID cleared");
248
262
  };
249
263
  handleUserObserverEphemeral = (event) => {
@@ -515,6 +529,113 @@ function getCodexExecutionFingerprint(mode) {
515
529
  });
516
530
  }
517
531
 
532
+ function getCodexSessionsRoot(options = {}) {
533
+ const codexHomeDir = options.codexHomeDir || process.env.CODEX_HOME || join(os.homedir(), ".codex");
534
+ return join(codexHomeDir, "sessions");
535
+ }
536
+ function collectFilesRecursive(dir, acc = []) {
537
+ let entries;
538
+ try {
539
+ entries = fs.readdirSync(dir, { withFileTypes: true });
540
+ } catch {
541
+ return acc;
542
+ }
543
+ for (const entry of entries) {
544
+ const full = join(dir, entry.name);
545
+ if (entry.isDirectory()) {
546
+ collectFilesRecursive(full, acc);
547
+ } else if (entry.isFile()) {
548
+ acc.push(full);
549
+ }
550
+ }
551
+ return acc;
552
+ }
553
+ function extractCodexSessionIdFromPath(filePath) {
554
+ 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$/);
555
+ return match?.[1] ?? null;
556
+ }
557
+ function normalizePathForSessionMatch(path) {
558
+ const normalizedPath = normalize(path).replace(/[\\/]+/g, "/").replace(/\/+$/, "");
559
+ return process.platform === "win32" ? normalizedPath.toLowerCase() : normalizedPath;
560
+ }
561
+ function extractCodexSessionCwdFromFile(filePath) {
562
+ let contents;
563
+ try {
564
+ contents = fs.readFileSync(filePath, "utf8");
565
+ } catch {
566
+ return null;
567
+ }
568
+ for (const line of contents.split(/\r?\n/)) {
569
+ if (!line.trim()) {
570
+ continue;
571
+ }
572
+ let record;
573
+ try {
574
+ record = JSON.parse(line);
575
+ } catch {
576
+ continue;
577
+ }
578
+ if (record.type !== "session_meta" || typeof record.payload !== "object" || record.payload === null) {
579
+ continue;
580
+ }
581
+ const cwd = record.payload.cwd;
582
+ return typeof cwd === "string" && cwd.trim().length > 0 ? cwd : null;
583
+ }
584
+ return null;
585
+ }
586
+ function codexSessionFileMatchesCwd(filePath, cwd) {
587
+ const expectedCwd = cwd?.trim();
588
+ if (!expectedCwd) {
589
+ return true;
590
+ }
591
+ const actualCwd = extractCodexSessionCwdFromFile(filePath);
592
+ if (!actualCwd) {
593
+ return false;
594
+ }
595
+ return normalizePathForSessionMatch(actualCwd) === normalizePathForSessionMatch(expectedCwd);
596
+ }
597
+ function findCodexResumeFile(sessionId, options = {}) {
598
+ if (!sessionId) return null;
599
+ try {
600
+ const candidates = collectFilesRecursive(getCodexSessionsRoot(options)).filter((full) => full.endsWith(`-${sessionId}.jsonl`)).filter((full) => {
601
+ try {
602
+ return fs.statSync(full).isFile();
603
+ } catch {
604
+ return false;
605
+ }
606
+ }).filter((full) => codexSessionFileMatchesCwd(full, options.cwd)).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
607
+ return candidates[0] || null;
608
+ } catch {
609
+ return null;
610
+ }
611
+ }
612
+ function isCodexResumeSessionValidForCwd(sessionId, cwd, options = {}) {
613
+ if (!sessionId?.trim()) {
614
+ return false;
615
+ }
616
+ return findCodexResumeFile(sessionId, { ...options, cwd }) !== null;
617
+ }
618
+ function findLatestCodexSessionIdSince(startTimeMs, options = {}) {
619
+ try {
620
+ const candidates = collectFilesRecursive(getCodexSessionsRoot(options)).filter((full) => full.endsWith(".jsonl")).filter((full) => {
621
+ try {
622
+ return fs.statSync(full).mtimeMs >= startTimeMs;
623
+ } catch {
624
+ return false;
625
+ }
626
+ }).filter((full) => codexSessionFileMatchesCwd(full, options.cwd)).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
627
+ for (const candidate of candidates) {
628
+ const sessionId = extractCodexSessionIdFromPath(candidate);
629
+ if (sessionId) {
630
+ return sessionId;
631
+ }
632
+ }
633
+ } catch {
634
+ return null;
635
+ }
636
+ return null;
637
+ }
638
+
518
639
  function delay(ms) {
519
640
  return new Promise((resolve) => setTimeout(resolve, ms));
520
641
  }
@@ -690,6 +811,17 @@ function shouldInjectLargeOutputRecoveryHint(error) {
690
811
  ].filter(Boolean).join("\n").toLowerCase();
691
812
  return searchableText.includes("memory allocation of") || searchableText.includes("out of memory") || searchableText.includes("oversized command output");
692
813
  }
814
+ function isCodexUnknownResumeSessionError(error) {
815
+ const record = typeof error === "object" && error !== null ? error : null;
816
+ const searchableText = [
817
+ formatDisplayMessage(error).trim(),
818
+ record ? formatDisplayMessage(record.message).trim() : "",
819
+ record ? formatDisplayMessage(record.stderr).trim() : "",
820
+ record ? formatDisplayMessage(record.detail).trim() : "",
821
+ record ? formatDisplayMessage(record.data).trim() : ""
822
+ ].filter(Boolean).join("\n");
823
+ 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);
824
+ }
693
825
  function normalizeCodexBackendError(error) {
694
826
  const record = typeof error === "object" && error !== null ? error : null;
695
827
  const text = formatDisplayMessage(error).trim();
@@ -1173,6 +1305,11 @@ async function codexRemoteLauncher(session) {
1173
1305
  };
1174
1306
  const createRuntimeHandle = async (mode) => {
1175
1307
  const executionMode = resolveCodexAcpExecutionMode(mode);
1308
+ const resumeSessionId = session.sessionId && isCodexResumeSessionValidForCwd(session.sessionId, session.path) ? session.sessionId : null;
1309
+ if (session.sessionId && !resumeSessionId) {
1310
+ logger.debug(`[Codex] Refusing to resume Codex session ${session.sessionId} because its local session file does not match cwd ${session.path}`);
1311
+ session.clearSessionId();
1312
+ }
1176
1313
  const validation = validateCodexAcpSpawn({
1177
1314
  command: process.env.HAPPY_CODEX_ACP_COMMAND,
1178
1315
  model: executionMode.model,
@@ -1193,7 +1330,7 @@ async function codexRemoteLauncher(session) {
1193
1330
  sandbox: executionMode.sandbox,
1194
1331
  approvalPolicy: executionMode.approvalPolicy,
1195
1332
  permissionHandler,
1196
- resumeSessionId: session.sessionId,
1333
+ resumeSessionId,
1197
1334
  selectionHandler: {
1198
1335
  handleSelection: (request) => selectionHandler.requestSelection(request)
1199
1336
  }
@@ -1344,12 +1481,31 @@ async function codexRemoteLauncher(session) {
1344
1481
  const turnSignal = abortController.signal;
1345
1482
  let sentTurnResultPush = false;
1346
1483
  let turnStatus = "task_complete";
1484
+ let retryingWithFreshSession = false;
1347
1485
  currentHappyOrgTurn = message.mode.happyOrg ?? null;
1348
1486
  try {
1349
1487
  turnInFlight = true;
1350
1488
  shouldCommitAccumulatedResponse = false;
1351
1489
  const isNewRuntimeSession = runtimeHandle === null;
1352
- const activeRuntimeHandle = runtimeHandle ?? await createRuntimeHandle(message.mode);
1490
+ const attemptedResumeSessionId = isNewRuntimeSession ? session.sessionId : null;
1491
+ let activeRuntimeHandle;
1492
+ try {
1493
+ activeRuntimeHandle = runtimeHandle ?? await createRuntimeHandle(message.mode);
1494
+ } catch (error) {
1495
+ if (!isNewRuntimeSession || !attemptedResumeSessionId || !isCodexUnknownResumeSessionError(error) || turnSignal.aborted) {
1496
+ throw error;
1497
+ }
1498
+ logger.debug(`[Codex] Resume session ${attemptedResumeSessionId} failed during ACP startup; retrying with a fresh session`, error);
1499
+ queueHistoryInjectionForRestart("Codex resume session is unavailable. Retrying with a fresh Codex session...");
1500
+ session.clearSessionId();
1501
+ currentModeHash = null;
1502
+ permissionHandler.reset();
1503
+ selectionHandler.reset();
1504
+ resetTurnState();
1505
+ pending = message;
1506
+ retryingWithFreshSession = true;
1507
+ continue;
1508
+ }
1353
1509
  if (!activeRuntimeHandle) {
1354
1510
  throw new Error("Failed to create Codex ACP backend");
1355
1511
  }
@@ -1376,8 +1532,25 @@ ${message.message}` : message.message;
1376
1532
  promptToSend = buildHappyOrgTurnPrompt(promptToSend, message.mode.happyOrg);
1377
1533
  }
1378
1534
  conversationHistory.addUserMessage(message.message);
1379
- await activeRuntimeHandle.sendPrompt(promptToSend);
1380
- await waitForResponseCompleteWithAbort(activeRuntimeHandle.backend, turnSignal);
1535
+ try {
1536
+ await activeRuntimeHandle.sendPrompt(promptToSend);
1537
+ await waitForResponseCompleteWithAbort(activeRuntimeHandle.backend, turnSignal);
1538
+ } catch (error) {
1539
+ if (!isNewRuntimeSession || !attemptedResumeSessionId || !isCodexUnknownResumeSessionError(error) || turnSignal.aborted) {
1540
+ throw error;
1541
+ }
1542
+ logger.debug(`[Codex] Resume session ${attemptedResumeSessionId} is unavailable; retrying with a fresh ACP session`, error);
1543
+ queueHistoryInjectionForRestart("Codex resume session is unavailable. Retrying with a fresh Codex session...");
1544
+ await disposeRuntimeHandle();
1545
+ session.clearSessionId();
1546
+ currentModeHash = null;
1547
+ permissionHandler.reset();
1548
+ selectionHandler.reset();
1549
+ resetTurnState();
1550
+ pending = message;
1551
+ retryingWithFreshSession = true;
1552
+ continue;
1553
+ }
1381
1554
  reasoningProcessor.completeCurrent();
1382
1555
  shouldCommitAccumulatedResponse = true;
1383
1556
  shouldInjectHistoryOnNextSession = false;
@@ -1407,6 +1580,9 @@ ${message.message}` : message.message;
1407
1580
  }
1408
1581
  } finally {
1409
1582
  turnInFlight = false;
1583
+ if (retryingWithFreshSession) {
1584
+ continue;
1585
+ }
1410
1586
  const finalizedTurn = await finalizeHappyOrgTurnWithBusinessAck({
1411
1587
  metadata: session.runtimeSession.getMetadataSnapshot?.() ?? null,
1412
1588
  queuedTurn: message.mode.happyOrg,
@@ -1502,52 +1678,6 @@ ${message.message}` : message.message;
1502
1678
  return shouldSwitchToLocal ? "switch" : "exit";
1503
1679
  }
1504
1680
 
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
1681
  const MANAGED_CODEX_HOME_PREFIX = "happy-codex-home-";
1552
1682
  function isManagedCodexHomePath(value) {
1553
1683
  const trimmed = value?.trim();
@@ -1614,12 +1744,17 @@ function terminateCodexLocalChild(pid) {
1614
1744
  }
1615
1745
  async function codexLocal(opts) {
1616
1746
  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
1747
  const codexEnv = buildCodexLocalEnv(process.env);
1622
1748
  const codexHomeDir = resolveCodexLocalHomeDir(codexEnv);
1749
+ const resumeSessionId = opts.sessionId && isCodexResumeSessionValidForCwd(opts.sessionId, opts.path, { codexHomeDir }) ? opts.sessionId : null;
1750
+ if (opts.sessionId && !resumeSessionId) {
1751
+ logger.debug(`[CodexLocal] Refusing to resume Codex session ${opts.sessionId} because its local session file does not match cwd ${opts.path}`);
1752
+ opts.onResumeSessionInvalid?.(opts.sessionId);
1753
+ }
1754
+ const args = resumeSessionId ? ["resume", resumeSessionId, ...baseArgs] : baseArgs;
1755
+ const startTime = Date.now();
1756
+ let detectedSessionId = resumeSessionId;
1757
+ const codexExecutable = resolveCodexExecutable();
1623
1758
  logger.debug(`[CodexLocal] Spawning ${codexExecutable} with args: ${JSON.stringify(args)}`);
1624
1759
  releaseTerminalInputForChild();
1625
1760
  clearTerminalForChild();
@@ -1645,7 +1780,7 @@ async function codexLocal(opts) {
1645
1780
  if (detectedSessionId) {
1646
1781
  return;
1647
1782
  }
1648
- const nextSessionId = findLatestCodexSessionIdSince(startTime, { codexHomeDir });
1783
+ const nextSessionId = findLatestCodexSessionIdSince(startTime, { codexHomeDir, cwd: opts.path });
1649
1784
  if (nextSessionId) {
1650
1785
  detectedSessionId = nextSessionId;
1651
1786
  logger.debug(`[CodexLocal] Detected session ID: ${nextSessionId}`);
@@ -1788,6 +1923,7 @@ async function codexLocalLauncher(session) {
1788
1923
  abort: processAbortController.signal,
1789
1924
  codexArgs: session.codexArgs,
1790
1925
  onSessionFound: recordSessionFound,
1926
+ onResumeSessionInvalid: session.clearSessionId,
1791
1927
  onThinkingChange: session.onThinkingChange
1792
1928
  });
1793
1929
  if (sessionId) {
@@ -1861,58 +1997,6 @@ async function codexLoop(opts) {
1861
1997
  });
1862
1998
  }
1863
1999
 
1864
- function normalizeFatalProcessError(error) {
1865
- if (error instanceof Error) {
1866
- return error;
1867
- }
1868
- const message = typeof error === "string" ? error : (() => {
1869
- try {
1870
- const serialized = JSON.stringify(error);
1871
- return serialized && serialized !== "{}" ? serialized : String(error);
1872
- } catch {
1873
- return String(error);
1874
- }
1875
- })();
1876
- return new Error(message || "Unknown fatal process error");
1877
- }
1878
- function installFatalProcessHandlers(options) {
1879
- const target = options.target ?? process;
1880
- const exit = options.exit ?? process.exit;
1881
- let shuttingDown = false;
1882
- let disposed = false;
1883
- const handleFatalEvent = async (event, reason) => {
1884
- const error = normalizeFatalProcessError(reason);
1885
- logger.debug(`[${options.label}] Fatal ${event}`, error);
1886
- if (shuttingDown) {
1887
- logger.debug(`[${options.label}] Ignoring duplicate fatal ${event} while shutdown is already in progress`);
1888
- return;
1889
- }
1890
- shuttingDown = true;
1891
- try {
1892
- await options.cleanup({ event, error });
1893
- } catch (cleanupError) {
1894
- logger.debug(`[${options.label}] Fatal cleanup failed`, cleanupError);
1895
- }
1896
- exit(1);
1897
- };
1898
- const onUncaughtException = (error) => {
1899
- void handleFatalEvent("uncaughtException", error);
1900
- };
1901
- const onUnhandledRejection = (reason) => {
1902
- void handleFatalEvent("unhandledRejection", reason);
1903
- };
1904
- target.on("uncaughtException", onUncaughtException);
1905
- target.on("unhandledRejection", onUnhandledRejection);
1906
- return () => {
1907
- if (disposed) {
1908
- return;
1909
- }
1910
- disposed = true;
1911
- target.off("uncaughtException", onUncaughtException);
1912
- target.off("unhandledRejection", onUnhandledRejection);
1913
- };
1914
- }
1915
-
1916
2000
  function normalizeResumeSessionId(value) {
1917
2001
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
1918
2002
  }
@@ -1957,6 +2041,17 @@ function normalizeCodexArgsForHappy(options) {
1957
2041
  };
1958
2042
  }
1959
2043
 
2044
+ function normalizePathForCodexResumeIdentity(path) {
2045
+ const normalized = path.trim().replace(/[\\/]+/g, "/").replace(/\/+$/, "");
2046
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
2047
+ }
2048
+ function codexResumeCwdMatches(expected, actual) {
2049
+ const normalizedExpected = expected?.trim();
2050
+ if (!normalizedExpected) {
2051
+ return true;
2052
+ }
2053
+ return normalizePathForCodexResumeIdentity(normalizedExpected) === normalizePathForCodexResumeIdentity(actual);
2054
+ }
1960
2055
  function resolveInitialCodexPermissionMode(opts) {
1961
2056
  if (opts.permissionMode) {
1962
2057
  return opts.permissionMode;
@@ -2056,6 +2151,20 @@ async function runCodex(opts) {
2056
2151
  }
2057
2152
  });
2058
2153
  sessionClient = initialSession;
2154
+ const metadataCodexSessionCwd = typeof metadata.codexSessionCwd === "string" ? metadata.codexSessionCwd : null;
2155
+ const requestedResumeSessionId = normalizedArgs.resumeSessionId;
2156
+ const requestedResumeCwd = opts.resumeSessionCwd ?? metadataCodexSessionCwd;
2157
+ const resumeSessionMatchesCwd = requestedResumeSessionId ? codexResumeCwdMatches(requestedResumeCwd, metadata.path) && isCodexResumeSessionValidForCwd(requestedResumeSessionId, metadata.path) : false;
2158
+ const initialCodexSessionId = requestedResumeSessionId && resumeSessionMatchesCwd ? requestedResumeSessionId : null;
2159
+ if (requestedResumeSessionId && !resumeSessionMatchesCwd) {
2160
+ logger.debug(`[codex] Ignoring resume session ${requestedResumeSessionId} because it does not match cwd ${metadata.path}`);
2161
+ sessionClient.updateMetadata((currentMetadata) => {
2162
+ const nextMetadata = { ...currentMetadata };
2163
+ delete nextMetadata.codexSessionId;
2164
+ delete nextMetadata.codexSessionCwd;
2165
+ return nextMetadata;
2166
+ });
2167
+ }
2059
2168
  const messageQueue = new MessageQueue2(getCodexExecutionFingerprint);
2060
2169
  let currentPermissionMode = initialPermissionMode;
2061
2170
  let currentModel;
@@ -2102,7 +2211,7 @@ async function runCodex(opts) {
2102
2211
  path: metadata.path,
2103
2212
  logPath: logger.logFilePath,
2104
2213
  startupRolePrompt: happyOrgStartupBinding?.identityPrompt ?? null,
2105
- sessionId: normalizedArgs.resumeSessionId,
2214
+ sessionId: initialCodexSessionId,
2106
2215
  mode: requestedStartingMode,
2107
2216
  messageQueue,
2108
2217
  codexArgs: normalizedArgs.codexArgs,