happy-imou-cloud 2.0.2 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BaseReasoningProcessor-D8VhEbs2.mjs → BaseReasoningProcessor-B37yOHxo.mjs} +2 -2
- package/dist/{BaseReasoningProcessor-B6tJ_eL5.cjs → BaseReasoningProcessor-_wxlqKB8.cjs} +4 -4
- package/dist/{api-D2Njw9Im.cjs → api-D9dIR956.cjs} +43 -43
- package/dist/{api-MYhAGPLn.mjs → api-DpQIC-DJ.mjs} +2 -2
- package/dist/{command-CVldr51S.cjs → command-CdXv1zNF.cjs} +3 -3
- package/dist/{command-nmK6O-ab.mjs → command-DRqrBuHM.mjs} +3 -3
- package/dist/{index-B97L7qLD.mjs → index-CriPm_z9.mjs} +8 -8
- package/dist/{index-Bg-YziG2.cjs → index-LYPXVO_L.cjs} +95 -95
- 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-Dkm7rm8k.mjs → persistence-CqgPgbzN.mjs} +1 -1
- package/dist/{persistence-D_2GkJAO.cjs → persistence-PzKU0QCa.cjs} +28 -28
- package/dist/{registerKillSessionHandler-BAXmJQRt.cjs → registerKillSessionHandler-BDBPoQSA.cjs} +2 -2
- package/dist/{registerKillSessionHandler-5GbrO0FM.mjs → registerKillSessionHandler-C3M_-4Zg.mjs} +2 -2
- package/dist/{runClaude-Cii3R2Fv.mjs → runClaude-D6Pdkevn.mjs} +25 -5
- package/dist/{runClaude-B-GNEkKg.cjs → runClaude-IeRSC5qX.cjs} +53 -33
- package/dist/{runCodex-C--ZwAhl.mjs → runCodex-CsfUU1Wb.mjs} +216 -401
- package/dist/{runCodex-CPHyGwj9.cjs → runCodex-WRmgSK6L.cjs} +216 -401
- package/dist/{runGemini-CQp7Nuzn.mjs → runGemini-CrH3dQ0Y.mjs} +5 -5
- package/dist/{runGemini-DaDz1bzQ.cjs → runGemini-qBh6zs5G.cjs} +5 -5
- 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-DpQIC-DJ.mjs';
|
|
3
|
+
import { readSettings } from './persistence-CqgPgbzN.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-CriPm_z9.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-B37yOHxo.mjs';
|
|
6
|
+
import { h as hashObject, a as MessageBuffer, r as registerKillSessionHandler, M as MessageQueue2 } from './registerKillSessionHandler-C3M_-4Zg.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";
|
|
@@ -897,6 +763,9 @@ function normalizeCodexBackendError(error) {
|
|
|
897
763
|
}
|
|
898
764
|
return text || "Codex backend exited unexpectedly";
|
|
899
765
|
}
|
|
766
|
+
function getCodexLegacySwitchIgnoredMessage() {
|
|
767
|
+
return "Codex now runs in ACP-only mode. Staying in the current session.";
|
|
768
|
+
}
|
|
900
769
|
function shouldEmitCodexReadyAfterWait(opts) {
|
|
901
770
|
if (opts.readyAlreadySent) {
|
|
902
771
|
return false;
|
|
@@ -923,58 +792,10 @@ function handleIncomingCodexMessageDuringRemoteTurn(opts) {
|
|
|
923
792
|
}
|
|
924
793
|
return total > 0 || interruptedTurn;
|
|
925
794
|
}
|
|
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
795
|
async function codexRemoteLauncher(session) {
|
|
974
796
|
const messageBuffer = new MessageBuffer();
|
|
975
797
|
const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
|
|
976
798
|
let inkInstance = null;
|
|
977
|
-
let exitReason = null;
|
|
978
799
|
let shouldExit = false;
|
|
979
800
|
let abortController = new AbortController();
|
|
980
801
|
let turnInFlight = false;
|
|
@@ -987,8 +808,10 @@ async function codexRemoteLauncher(session) {
|
|
|
987
808
|
let accumulatedResponse = "";
|
|
988
809
|
let isResponseInProgress = false;
|
|
989
810
|
let taskStartedSent = false;
|
|
811
|
+
let shouldInjectHistoryOnNextSession = false;
|
|
990
812
|
const permissionHandler = new CodexPermissionHandler(session.client);
|
|
991
813
|
const selectionHandler = new CodexSelectionHandler(session.client);
|
|
814
|
+
const conversationHistory = new ConversationHistory({ maxMessages: 20, maxCharacters: 5e4 });
|
|
992
815
|
const reasoningProcessor = new ReasoningProcessor((message) => {
|
|
993
816
|
session.runtimeSession.sendCodexMessage(message);
|
|
994
817
|
});
|
|
@@ -1041,10 +864,26 @@ async function codexRemoteLauncher(session) {
|
|
|
1041
864
|
logger.debug("[Codex] Error disposing ACP backend:", error);
|
|
1042
865
|
}
|
|
1043
866
|
};
|
|
867
|
+
const emitStatusMessage = (message) => {
|
|
868
|
+
messageBuffer.addMessage(message, "status");
|
|
869
|
+
session.runtimeSession.sendSessionEvent({ type: "message", message });
|
|
870
|
+
};
|
|
871
|
+
const queueHistoryInjectionForRestart = (reason) => {
|
|
872
|
+
messageBuffer.addMessage("\u2550".repeat(40), "status");
|
|
873
|
+
if (conversationHistory.hasHistory()) {
|
|
874
|
+
shouldInjectHistoryOnNextSession = true;
|
|
875
|
+
const message = `${reason} Preserving ${conversationHistory.size()} earlier messages of context.`;
|
|
876
|
+
emitStatusMessage(message);
|
|
877
|
+
logger.debug(`[Codex] Will inject conversation history after restart: ${conversationHistory.getSummary()}`);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
emitStatusMessage(reason);
|
|
881
|
+
};
|
|
1044
882
|
const emitFinalAssistantMessage = () => {
|
|
1045
883
|
if (!accumulatedResponse.trim()) {
|
|
1046
884
|
return;
|
|
1047
885
|
}
|
|
886
|
+
conversationHistory.addAssistantMessage(accumulatedResponse);
|
|
1048
887
|
session.runtimeSession.sendCodexMessage({
|
|
1049
888
|
type: "message",
|
|
1050
889
|
message: accumulatedResponse,
|
|
@@ -1259,11 +1098,9 @@ async function codexRemoteLauncher(session) {
|
|
|
1259
1098
|
return result.backend;
|
|
1260
1099
|
};
|
|
1261
1100
|
const handleSwitchToLocal = async () => {
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
shouldExit = true;
|
|
1266
|
-
await handleAbort();
|
|
1101
|
+
const message = getCodexLegacySwitchIgnoredMessage();
|
|
1102
|
+
logger.debug("[Codex] Ignoring legacy switch request because Codex is ACP-only");
|
|
1103
|
+
emitStatusMessage(message);
|
|
1267
1104
|
};
|
|
1268
1105
|
session.setPendingInteractionSuperseder((reason = INTERACTION_SUPERSEDED_ERROR) => {
|
|
1269
1106
|
return handleIncomingCodexMessageDuringRemoteTurn({
|
|
@@ -1324,17 +1161,9 @@ async function codexRemoteLauncher(session) {
|
|
|
1324
1161
|
inkInstance = render(React.createElement(CodexDisplay, {
|
|
1325
1162
|
messageBuffer,
|
|
1326
1163
|
logPath: process.env.DEBUG ? session.logPath : void 0,
|
|
1327
|
-
title: "
|
|
1164
|
+
title: "Codex Agent Messages",
|
|
1328
1165
|
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";
|
|
1166
|
+
logger.debug("[Codex] Exiting Codex ACP session from keyboard");
|
|
1338
1167
|
shouldExit = true;
|
|
1339
1168
|
await handleAbort();
|
|
1340
1169
|
}
|
|
@@ -1374,8 +1203,7 @@ async function codexRemoteLauncher(session) {
|
|
|
1374
1203
|
break;
|
|
1375
1204
|
}
|
|
1376
1205
|
if (wasSessionCreated && currentModeHash && message.hash !== currentModeHash) {
|
|
1377
|
-
|
|
1378
|
-
messageBuffer.addMessage("Starting new Codex session (mode changed)...", "status");
|
|
1206
|
+
queueHistoryInjectionForRestart("Starting new Codex session (execution settings changed)...");
|
|
1379
1207
|
await disposeBackend();
|
|
1380
1208
|
session.clearSessionId();
|
|
1381
1209
|
currentModeHash = null;
|
|
@@ -1402,27 +1230,33 @@ async function codexRemoteLauncher(session) {
|
|
|
1402
1230
|
session.onSessionFound(sessionId);
|
|
1403
1231
|
}
|
|
1404
1232
|
session.onThinkingChange(true);
|
|
1405
|
-
|
|
1233
|
+
let promptToSend = message.message;
|
|
1234
|
+
if (shouldInjectHistoryOnNextSession && conversationHistory.hasHistory()) {
|
|
1235
|
+
const historyContext = conversationHistory.getContextForNewSession();
|
|
1236
|
+
promptToSend = historyContext + promptToSend;
|
|
1237
|
+
logger.debug(`[Codex] Injected conversation history context (${historyContext.length} chars)`);
|
|
1238
|
+
}
|
|
1239
|
+
conversationHistory.addUserMessage(message.message);
|
|
1240
|
+
await activeBackend.sendPrompt(acpSessionId, promptToSend);
|
|
1406
1241
|
await waitForResponseCompleteWithAbort(activeBackend, turnSignal);
|
|
1407
1242
|
reasoningProcessor.completeCurrent();
|
|
1243
|
+
shouldInjectHistoryOnNextSession = false;
|
|
1408
1244
|
} catch (error) {
|
|
1409
1245
|
logger.warn("Error in codex ACP session:", error);
|
|
1410
1246
|
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) {
|
|
1247
|
+
const isExpectedInterruption = isAbortError || turnSignal.aborted || shouldExit;
|
|
1248
|
+
if (isExpectedInterruption) {
|
|
1416
1249
|
session.runtimeSession.sendCodexMessage({
|
|
1417
1250
|
type: "turn_aborted",
|
|
1418
1251
|
id: randomUUID()
|
|
1419
1252
|
});
|
|
1420
|
-
|
|
1421
|
-
session.runtimeSession.sendSessionEvent({ type: "message", message: "Aborted by user" });
|
|
1253
|
+
emitStatusMessage("Aborted by user");
|
|
1422
1254
|
} else {
|
|
1423
1255
|
const errorMessage = normalizeCodexBackendError(error);
|
|
1424
|
-
|
|
1425
|
-
|
|
1256
|
+
emitStatusMessage(errorMessage);
|
|
1257
|
+
if (conversationHistory.hasHistory()) {
|
|
1258
|
+
shouldInjectHistoryOnNextSession = true;
|
|
1259
|
+
}
|
|
1426
1260
|
await disposeBackend();
|
|
1427
1261
|
session.clearSessionId();
|
|
1428
1262
|
}
|
|
@@ -1476,33 +1310,16 @@ async function codexRemoteLauncher(session) {
|
|
|
1476
1310
|
}
|
|
1477
1311
|
messageBuffer.clear();
|
|
1478
1312
|
}
|
|
1479
|
-
logger.debug(
|
|
1480
|
-
return
|
|
1313
|
+
logger.debug("[Codex] ACP remote launcher returning: exit");
|
|
1314
|
+
return "exit";
|
|
1481
1315
|
}
|
|
1482
1316
|
|
|
1483
1317
|
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
|
-
}
|
|
1318
|
+
const displayMode = opts.startingMode ?? "local";
|
|
1319
|
+
await opts.session.onModeChange(displayMode);
|
|
1320
|
+
logger.debug(`[codex-loop] Starting ACP-only Codex launcher with display mode: ${displayMode}`);
|
|
1321
|
+
await codexRemoteLauncher(opts.session);
|
|
1322
|
+
return 0;
|
|
1506
1323
|
}
|
|
1507
1324
|
|
|
1508
1325
|
function supportsAgentStateUpdateEvents(sessionClient) {
|
|
@@ -1543,16 +1360,17 @@ async function syncControlledByUserState(sessionClient, controlledByUser) {
|
|
|
1543
1360
|
});
|
|
1544
1361
|
}
|
|
1545
1362
|
function shouldSupersedeCodexPendingInteractions(opts) {
|
|
1546
|
-
return
|
|
1363
|
+
return true;
|
|
1547
1364
|
}
|
|
1548
1365
|
async function runCodex(opts) {
|
|
1549
1366
|
const sessionTag = randomUUID();
|
|
1550
1367
|
connectionState.setBackend("Codex");
|
|
1551
|
-
|
|
1368
|
+
const requestedStartingMode = opts.startingMode ?? (opts.startedBy === "daemon" ? "remote" : "local");
|
|
1369
|
+
if (opts.startedBy === "daemon" && requestedStartingMode === "local") {
|
|
1552
1370
|
throw new Error("Daemon-spawned Codex sessions cannot use local mode.");
|
|
1553
1371
|
}
|
|
1554
1372
|
const api = await ApiClient.create(opts.credentials);
|
|
1555
|
-
logger.debug(`[codex] Starting with options: startedBy=${opts.startedBy || "terminal"},
|
|
1373
|
+
logger.debug(`[codex] Starting with options: startedBy=${opts.startedBy || "terminal"}, requestedStartingMode=${requestedStartingMode}, executionMode=acp-only`);
|
|
1556
1374
|
const settings = await readSettings();
|
|
1557
1375
|
const machineId = settings?.machineId;
|
|
1558
1376
|
if (!machineId) {
|
|
@@ -1601,10 +1419,7 @@ async function runCodex(opts) {
|
|
|
1601
1419
|
logger.debug("[START] Failed to report to daemon:", error);
|
|
1602
1420
|
}
|
|
1603
1421
|
}
|
|
1604
|
-
const messageQueue = new MessageQueue2(
|
|
1605
|
-
permissionMode: mode.permissionMode,
|
|
1606
|
-
model: mode.model
|
|
1607
|
-
}));
|
|
1422
|
+
const messageQueue = new MessageQueue2(getCodexExecutionFingerprint);
|
|
1608
1423
|
let currentPermissionMode;
|
|
1609
1424
|
let currentModel;
|
|
1610
1425
|
sessionClient.onUserMessage((message) => {
|
|
@@ -1636,7 +1451,7 @@ async function runCodex(opts) {
|
|
|
1636
1451
|
path: process.cwd(),
|
|
1637
1452
|
logPath: logger.logFilePath,
|
|
1638
1453
|
sessionId: null,
|
|
1639
|
-
mode:
|
|
1454
|
+
mode: requestedStartingMode,
|
|
1640
1455
|
messageQueue,
|
|
1641
1456
|
codexArgs: opts.codexArgs,
|
|
1642
1457
|
onModeChange: async (mode) => {
|
|
@@ -1647,7 +1462,7 @@ async function runCodex(opts) {
|
|
|
1647
1462
|
try {
|
|
1648
1463
|
const exitCode = await codexLoop({
|
|
1649
1464
|
session: codexSession,
|
|
1650
|
-
startingMode:
|
|
1465
|
+
startingMode: requestedStartingMode
|
|
1651
1466
|
});
|
|
1652
1467
|
sessionClient.sendSessionDeath();
|
|
1653
1468
|
await sessionClient.flush();
|