happy-imou-cloud 2.1.49 → 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.
- package/dist/{BaseReasoningProcessor-CAVeOdyo.mjs → BaseReasoningProcessor-2qoX8OA1.mjs} +2 -2
- package/dist/{BaseReasoningProcessor-Dn9NcoHz.cjs → BaseReasoningProcessor-D3q5lh9h.cjs} +2 -2
- package/dist/{ProviderSelectionHandler-BJJc7qOR.cjs → ProviderSelectionHandler-CO5BYEgC.cjs} +2 -2
- package/dist/{ProviderSelectionHandler-DIYidT13.mjs → ProviderSelectionHandler-CO6foZET.mjs} +2 -2
- package/dist/{api-DnqaNvyV.mjs → api-6B4EMs47.mjs} +1 -1
- package/dist/{api-D7nAeZi7.cjs → api-D4JOaMll.cjs} +1 -1
- package/dist/{command-CzfRRhVe.mjs → command-BbJCdR2t.mjs} +2 -2
- package/dist/{command-VcH4hbhi.cjs → command-DuKDmbdM.cjs} +2 -2
- package/dist/{index-xa1kwZoj.cjs → index-CZJH0CUT.cjs} +21 -12
- package/dist/{index-7Z93BoVn.mjs → index-CyGHrYHl.mjs} +18 -9
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +88 -86
- package/dist/lib.d.mts +88 -86
- package/dist/lib.mjs +1 -1
- package/dist/{registerKillSessionHandler-CfCya6si.cjs → registerKillSessionHandler-Bo_MMbdY.cjs} +2 -2
- package/dist/{registerKillSessionHandler-DLDg2EES.mjs → registerKillSessionHandler-ggfGac_S.mjs} +2 -2
- package/dist/{runClaude-BBGNmGj6.cjs → runClaude-B4eoG3Pu.cjs} +4 -4
- package/dist/{runClaude-zCwRhpOw.mjs → runClaude-jDr3i0bO.mjs} +4 -4
- package/dist/{runCodex-BbgLVjb9.mjs → runCodex-e_D1Jd_w.mjs} +217 -63
- package/dist/{runCodex-jUU6U2tZ.cjs → runCodex-hDGeZAh5.cjs} +216 -62
- package/dist/{runGemini-C0NT8MHK.cjs → runGemini-CeA9aiD1.cjs} +4 -4
- package/dist/{runGemini-DcwNsudA.mjs → runGemini-Cur84FvO.mjs} +4 -4
- package/package.json +1 -1
|
@@ -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-
|
|
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-
|
|
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-
|
|
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,
|
|
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-
|
|
28
|
-
import { B as BaseReasoningProcessor, b as bootstrapManagedProviderSession } from './BaseReasoningProcessor-
|
|
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
|
|
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
|
|
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
|
-
|
|
1380
|
-
|
|
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) {
|
|
@@ -1957,6 +2086,17 @@ function normalizeCodexArgsForHappy(options) {
|
|
|
1957
2086
|
};
|
|
1958
2087
|
}
|
|
1959
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
|
+
}
|
|
1960
2100
|
function resolveInitialCodexPermissionMode(opts) {
|
|
1961
2101
|
if (opts.permissionMode) {
|
|
1962
2102
|
return opts.permissionMode;
|
|
@@ -2056,6 +2196,20 @@ async function runCodex(opts) {
|
|
|
2056
2196
|
}
|
|
2057
2197
|
});
|
|
2058
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
|
+
}
|
|
2059
2213
|
const messageQueue = new MessageQueue2(getCodexExecutionFingerprint);
|
|
2060
2214
|
let currentPermissionMode = initialPermissionMode;
|
|
2061
2215
|
let currentModel;
|
|
@@ -2102,7 +2256,7 @@ async function runCodex(opts) {
|
|
|
2102
2256
|
path: metadata.path,
|
|
2103
2257
|
logPath: logger.logFilePath,
|
|
2104
2258
|
startupRolePrompt: happyOrgStartupBinding?.identityPrompt ?? null,
|
|
2105
|
-
sessionId:
|
|
2259
|
+
sessionId: initialCodexSessionId,
|
|
2106
2260
|
mode: requestedStartingMode,
|
|
2107
2261
|
messageQueue,
|
|
2108
2262
|
codexArgs: normalizedArgs.codexArgs,
|