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