happy-imou-cloud 2.0.1 → 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-DQE2l7Xu.mjs → BaseReasoningProcessor-B37yOHxo.mjs} +2 -2
- package/dist/{BaseReasoningProcessor-01KqA3Kz.cjs → BaseReasoningProcessor-_wxlqKB8.cjs} +4 -4
- package/dist/{api-B8v4tczT.cjs → api-D9dIR956.cjs} +97 -44
- package/dist/{api-B5Ui8Fw0.mjs → api-DpQIC-DJ.mjs} +56 -3
- package/dist/{command-D8yNlaDo.cjs → command-CdXv1zNF.cjs} +3 -3
- package/dist/{command-BfIuJmeo.mjs → command-DRqrBuHM.mjs} +3 -3
- package/dist/{index-BByhFIIq.mjs → index-CriPm_z9.mjs} +15 -17
- package/dist/{index-BOqJ9hwi.cjs → index-LYPXVO_L.cjs} +98 -100
- package/dist/index.cjs +3 -3
- package/dist/index.mjs +3 -3
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +6 -0
- package/dist/lib.d.mts +6 -0
- package/dist/lib.mjs +1 -1
- package/dist/{persistence-CzpZpiL3.mjs → persistence-CqgPgbzN.mjs} +29 -7
- package/dist/{persistence-C33AMdtv.cjs → persistence-PzKU0QCa.cjs} +54 -32
- package/dist/{registerKillSessionHandler-BkzQulD9.cjs → registerKillSessionHandler-BDBPoQSA.cjs} +2 -2
- package/dist/{registerKillSessionHandler-BtSK7IOa.mjs → registerKillSessionHandler-C3M_-4Zg.mjs} +2 -2
- package/dist/{runClaude-C_WLfM6c.mjs → runClaude-D6Pdkevn.mjs} +250 -46
- package/dist/{runClaude-CNVufgZb.cjs → runClaude-IeRSC5qX.cjs} +270 -66
- package/dist/{runCodex-8eWjTPJr.mjs → runCodex-CsfUU1Wb.mjs} +216 -401
- package/dist/{runCodex-Dzy8anlX.cjs → runCodex-WRmgSK6L.cjs} +216 -401
- package/dist/{runGemini-nbr0mm-S.mjs → runGemini-CrH3dQ0Y.mjs} +5 -5
- package/dist/{runGemini-CgsVKP7m.cjs → runGemini-qBh6zs5G.cjs} +5 -5
- package/package.json +1 -2
- package/dist/future-Dq4Ha1Dn.cjs +0 -24
- package/dist/future-xRdLl3vf.mjs +0 -22
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var node_crypto = require('node:crypto');
|
|
4
|
-
var api = require('./api-
|
|
5
|
-
var persistence = require('./persistence-
|
|
6
|
-
var index = require('./index-
|
|
7
|
-
var BaseReasoningProcessor = require('./BaseReasoningProcessor-
|
|
8
|
-
var registerKillSessionHandler = require('./registerKillSessionHandler-
|
|
9
|
-
var future = require('./future-Dq4Ha1Dn.cjs');
|
|
10
|
-
var node_child_process = require('node:child_process');
|
|
11
|
-
var fs = require('node:fs');
|
|
12
|
-
var os = require('node:os');
|
|
13
|
-
var path = require('node:path');
|
|
4
|
+
var api = require('./api-D9dIR956.cjs');
|
|
5
|
+
var persistence = require('./persistence-PzKU0QCa.cjs');
|
|
6
|
+
var index = require('./index-LYPXVO_L.cjs');
|
|
7
|
+
var BaseReasoningProcessor = require('./BaseReasoningProcessor-_wxlqKB8.cjs');
|
|
8
|
+
var registerKillSessionHandler = require('./registerKillSessionHandler-BDBPoQSA.cjs');
|
|
14
9
|
var React = require('react');
|
|
15
10
|
var ink = require('ink');
|
|
16
11
|
require('axios');
|
|
17
12
|
require('chalk');
|
|
18
13
|
require('fs');
|
|
14
|
+
require('node:fs');
|
|
15
|
+
require('node:os');
|
|
16
|
+
require('node:path');
|
|
19
17
|
require('node:events');
|
|
20
18
|
require('socket.io-client');
|
|
21
19
|
require('zod');
|
|
@@ -33,6 +31,7 @@ require('qrcode-terminal');
|
|
|
33
31
|
require('node:module');
|
|
34
32
|
require('open');
|
|
35
33
|
require('url');
|
|
34
|
+
require('node:child_process');
|
|
36
35
|
require('ps-list');
|
|
37
36
|
require('cross-spawn');
|
|
38
37
|
require('fastify');
|
|
@@ -130,7 +129,6 @@ class CodexSession {
|
|
|
130
129
|
sessionId;
|
|
131
130
|
mode;
|
|
132
131
|
thinking = false;
|
|
133
|
-
localModePinned = false;
|
|
134
132
|
keepAliveInterval;
|
|
135
133
|
onModeChangeCallback;
|
|
136
134
|
clientSwapCallbacks = [];
|
|
@@ -190,18 +188,6 @@ class CodexSession {
|
|
|
190
188
|
this.client.keepAlive(this.thinking, mode);
|
|
191
189
|
await this.onModeChangeCallback(mode);
|
|
192
190
|
};
|
|
193
|
-
pinLocalMode = () => {
|
|
194
|
-
this.localModePinned = true;
|
|
195
|
-
api.logger.debug("[CodexSession] Local mode pinned");
|
|
196
|
-
};
|
|
197
|
-
clearLocalModePin = () => {
|
|
198
|
-
if (!this.localModePinned) {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
this.localModePinned = false;
|
|
202
|
-
api.logger.debug("[CodexSession] Local mode pin cleared");
|
|
203
|
-
};
|
|
204
|
-
isLocalModePinned = () => this.localModePinned;
|
|
205
191
|
onSessionFound = (sessionId) => {
|
|
206
192
|
if (this.sessionId === sessionId) {
|
|
207
193
|
return;
|
|
@@ -219,262 +205,7 @@ class CodexSession {
|
|
|
219
205
|
};
|
|
220
206
|
}
|
|
221
207
|
|
|
222
|
-
|
|
223
|
-
const codexHomeDir = process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
|
|
224
|
-
return path.join(codexHomeDir, "sessions");
|
|
225
|
-
}
|
|
226
|
-
function collectFilesRecursive(dir, acc = []) {
|
|
227
|
-
let entries;
|
|
228
|
-
try {
|
|
229
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
230
|
-
} catch {
|
|
231
|
-
return acc;
|
|
232
|
-
}
|
|
233
|
-
for (const entry of entries) {
|
|
234
|
-
const full = path.join(dir, entry.name);
|
|
235
|
-
if (entry.isDirectory()) {
|
|
236
|
-
collectFilesRecursive(full, acc);
|
|
237
|
-
} else if (entry.isFile()) {
|
|
238
|
-
acc.push(full);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return acc;
|
|
242
|
-
}
|
|
243
|
-
function extractCodexSessionIdFromPath(filePath) {
|
|
244
|
-
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$/);
|
|
245
|
-
return match?.[1] ?? null;
|
|
246
|
-
}
|
|
247
|
-
function findLatestCodexSessionIdSince(startTimeMs) {
|
|
248
|
-
try {
|
|
249
|
-
const candidates = collectFilesRecursive(getCodexSessionsRoot()).filter((full) => full.endsWith(".jsonl")).filter((full) => {
|
|
250
|
-
try {
|
|
251
|
-
return fs.statSync(full).mtimeMs >= startTimeMs;
|
|
252
|
-
} catch {
|
|
253
|
-
return false;
|
|
254
|
-
}
|
|
255
|
-
}).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
|
|
256
|
-
for (const candidate of candidates) {
|
|
257
|
-
const sessionId = extractCodexSessionIdFromPath(candidate);
|
|
258
|
-
if (sessionId) {
|
|
259
|
-
return sessionId;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
} catch {
|
|
263
|
-
return null;
|
|
264
|
-
}
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function firstExistingPath(candidates) {
|
|
269
|
-
for (const candidate of candidates) {
|
|
270
|
-
try {
|
|
271
|
-
if (fs.existsSync(candidate)) {
|
|
272
|
-
return candidate;
|
|
273
|
-
}
|
|
274
|
-
} catch {
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
279
|
-
function resolveCodexExecutable() {
|
|
280
|
-
if (process.platform === "win32") {
|
|
281
|
-
const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
|
282
|
-
const npmGlobalBin = path.join(appData, "npm");
|
|
283
|
-
const resolved = firstExistingPath([
|
|
284
|
-
path.join(npmGlobalBin, "codex.cmd"),
|
|
285
|
-
path.join(npmGlobalBin, "codex.ps1"),
|
|
286
|
-
path.join(npmGlobalBin, "codex")
|
|
287
|
-
]);
|
|
288
|
-
if (resolved) {
|
|
289
|
-
return resolved;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
return "codex";
|
|
293
|
-
}
|
|
294
|
-
function shouldUseShellForCodex(executable) {
|
|
295
|
-
return process.platform === "win32" && /\.(cmd|bat|ps1)$/i.test(executable);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
class CodexExitCodeError extends Error {
|
|
299
|
-
exitCode;
|
|
300
|
-
constructor(exitCode) {
|
|
301
|
-
super(`Codex process exited with code: ${exitCode}`);
|
|
302
|
-
this.name = "CodexExitCodeError";
|
|
303
|
-
this.exitCode = exitCode;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
function terminateCodexLocalChild(pid) {
|
|
307
|
-
if (!pid) {
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
try {
|
|
311
|
-
if (process.platform === "win32") {
|
|
312
|
-
node_child_process.execFileSync("taskkill", ["/F", "/T", "/PID", pid.toString()], { stdio: "ignore" });
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
process.kill(pid, "SIGTERM");
|
|
316
|
-
} catch (error) {
|
|
317
|
-
api.logger.debug("[CodexLocal] Failed to terminate child process", error);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
async function codexLocal(opts) {
|
|
321
|
-
const baseArgs = [...opts.codexArgs ?? []];
|
|
322
|
-
const args = opts.sessionId ? ["resume", opts.sessionId, ...baseArgs] : baseArgs;
|
|
323
|
-
const startTime = Date.now();
|
|
324
|
-
let detectedSessionId = opts.sessionId;
|
|
325
|
-
const codexExecutable = resolveCodexExecutable();
|
|
326
|
-
api.logger.debug(`[CodexLocal] Spawning ${codexExecutable} with args: ${JSON.stringify(args)}`);
|
|
327
|
-
process.stdin.pause();
|
|
328
|
-
try {
|
|
329
|
-
await new Promise((resolve, reject) => {
|
|
330
|
-
const child = node_child_process.spawn(codexExecutable, args, {
|
|
331
|
-
cwd: opts.path,
|
|
332
|
-
env: process.env,
|
|
333
|
-
stdio: "inherit",
|
|
334
|
-
signal: opts.abort,
|
|
335
|
-
shell: shouldUseShellForCodex(codexExecutable)
|
|
336
|
-
});
|
|
337
|
-
const abortChild = () => {
|
|
338
|
-
if (child.exitCode === null) {
|
|
339
|
-
api.logger.debug(`[CodexLocal] Abort received, terminating child tree pid=${child.pid ?? "none"}`);
|
|
340
|
-
terminateCodexLocalChild(child.pid);
|
|
341
|
-
}
|
|
342
|
-
};
|
|
343
|
-
opts.abort.addEventListener("abort", abortChild, { once: true });
|
|
344
|
-
const sessionPoll = setInterval(() => {
|
|
345
|
-
if (detectedSessionId) {
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
const nextSessionId = findLatestCodexSessionIdSince(startTime);
|
|
349
|
-
if (nextSessionId) {
|
|
350
|
-
detectedSessionId = nextSessionId;
|
|
351
|
-
api.logger.debug(`[CodexLocal] Detected session ID: ${nextSessionId}`);
|
|
352
|
-
opts.onSessionFound(nextSessionId);
|
|
353
|
-
}
|
|
354
|
-
}, 1e3);
|
|
355
|
-
child.on("error", (error) => {
|
|
356
|
-
clearInterval(sessionPoll);
|
|
357
|
-
opts.abort.removeEventListener("abort", abortChild);
|
|
358
|
-
reject(error);
|
|
359
|
-
});
|
|
360
|
-
child.on("exit", (code, signal) => {
|
|
361
|
-
clearInterval(sessionPoll);
|
|
362
|
-
opts.abort.removeEventListener("abort", abortChild);
|
|
363
|
-
opts.onThinkingChange?.(false);
|
|
364
|
-
if (signal === "SIGTERM" && opts.abort.aborted) {
|
|
365
|
-
resolve();
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
if (signal) {
|
|
369
|
-
reject(new Error(`Codex terminated with signal: ${signal}`));
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
if (code !== 0 && code !== null) {
|
|
373
|
-
reject(new CodexExitCodeError(code));
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
resolve();
|
|
377
|
-
});
|
|
378
|
-
});
|
|
379
|
-
} finally {
|
|
380
|
-
process.stdin.resume();
|
|
381
|
-
opts.onThinkingChange?.(false);
|
|
382
|
-
}
|
|
383
|
-
return detectedSessionId;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
async function codexLocalLauncher(session) {
|
|
387
|
-
let exitReason = null;
|
|
388
|
-
const processAbortController = new AbortController();
|
|
389
|
-
const exitFuture = new future.Future();
|
|
390
|
-
try {
|
|
391
|
-
async function abort() {
|
|
392
|
-
if (!processAbortController.signal.aborted) {
|
|
393
|
-
processAbortController.abort();
|
|
394
|
-
}
|
|
395
|
-
await exitFuture.promise;
|
|
396
|
-
}
|
|
397
|
-
async function doSwitch() {
|
|
398
|
-
api.logger.debug("[codex-local]: switching to remote mode");
|
|
399
|
-
if (!exitReason) {
|
|
400
|
-
exitReason = { type: "switch" };
|
|
401
|
-
}
|
|
402
|
-
await abort();
|
|
403
|
-
}
|
|
404
|
-
async function doAbort() {
|
|
405
|
-
api.logger.debug("[codex-local]: abort requested");
|
|
406
|
-
if (!exitReason) {
|
|
407
|
-
exitReason = { type: "switch" };
|
|
408
|
-
}
|
|
409
|
-
session.queue.reset();
|
|
410
|
-
await abort();
|
|
411
|
-
}
|
|
412
|
-
session.client.rpcHandlerManager.registerHandler("abort", doAbort);
|
|
413
|
-
session.client.rpcHandlerManager.registerHandler("switch", doSwitch);
|
|
414
|
-
session.queue.setOnMessage(() => {
|
|
415
|
-
if (session.isLocalModePinned()) {
|
|
416
|
-
api.logger.debug("[codex-local]: message arrived while local mode is pinned; staying local");
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
void doSwitch();
|
|
420
|
-
});
|
|
421
|
-
if (session.queue.size() > 0) {
|
|
422
|
-
if (session.isLocalModePinned()) {
|
|
423
|
-
api.logger.debug("[codex-local]: pending queued messages detected, but local mode is pinned; staying local");
|
|
424
|
-
} else {
|
|
425
|
-
return { type: "switch" };
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
while (true) {
|
|
429
|
-
if (exitReason) {
|
|
430
|
-
return exitReason;
|
|
431
|
-
}
|
|
432
|
-
try {
|
|
433
|
-
const sessionId = await codexLocal({
|
|
434
|
-
path: session.path,
|
|
435
|
-
sessionId: session.sessionId,
|
|
436
|
-
abort: processAbortController.signal,
|
|
437
|
-
codexArgs: session.codexArgs,
|
|
438
|
-
onSessionFound: session.onSessionFound,
|
|
439
|
-
onThinkingChange: session.onThinkingChange
|
|
440
|
-
});
|
|
441
|
-
if (sessionId) {
|
|
442
|
-
session.onSessionFound(sessionId);
|
|
443
|
-
}
|
|
444
|
-
if (!exitReason) {
|
|
445
|
-
exitReason = { type: "exit", code: 0 };
|
|
446
|
-
break;
|
|
447
|
-
}
|
|
448
|
-
} catch (error) {
|
|
449
|
-
api.logger.debug("[codex-local]: launch error", error);
|
|
450
|
-
if (error instanceof CodexExitCodeError && !exitReason) {
|
|
451
|
-
exitReason = { type: "exit", code: error.exitCode };
|
|
452
|
-
break;
|
|
453
|
-
}
|
|
454
|
-
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "EINVAL") && !exitReason) {
|
|
455
|
-
exitReason = { type: "exit", code: 1 };
|
|
456
|
-
session.runtimeSession.sendSessionEvent({ type: "message", message: "Codex CLI not found. Check your local Codex installation." });
|
|
457
|
-
break;
|
|
458
|
-
}
|
|
459
|
-
if (!exitReason) {
|
|
460
|
-
session.runtimeSession.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
|
|
461
|
-
continue;
|
|
462
|
-
}
|
|
463
|
-
break;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
} finally {
|
|
467
|
-
exitFuture.resolve(void 0);
|
|
468
|
-
session.client.rpcHandlerManager.registerHandler("abort", async () => {
|
|
469
|
-
});
|
|
470
|
-
session.client.rpcHandlerManager.registerHandler("switch", async () => {
|
|
471
|
-
});
|
|
472
|
-
session.queue.setOnMessage(null);
|
|
473
|
-
}
|
|
474
|
-
return exitReason || { type: "exit", code: 0 };
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
const CodexDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal, title }) => {
|
|
208
|
+
const CodexDisplay = ({ messageBuffer, logPath, onExit, title }) => {
|
|
478
209
|
const [messages, setMessages] = React.useState([]);
|
|
479
210
|
const [confirmationMode, setConfirmationMode] = React.useState(null);
|
|
480
211
|
const [actionInProgress, setActionInProgress] = React.useState(null);
|
|
@@ -523,21 +254,10 @@ const CodexDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal, title }
|
|
|
523
254
|
}
|
|
524
255
|
return;
|
|
525
256
|
}
|
|
526
|
-
if (input === " ") {
|
|
527
|
-
if (confirmationMode === "switch") {
|
|
528
|
-
resetConfirmation();
|
|
529
|
-
setActionInProgress("switching");
|
|
530
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
531
|
-
onSwitchToLocal?.();
|
|
532
|
-
} else {
|
|
533
|
-
setConfirmationWithTimeout("switch");
|
|
534
|
-
}
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
257
|
if (confirmationMode) {
|
|
538
258
|
resetConfirmation();
|
|
539
259
|
}
|
|
540
|
-
}, [confirmationMode, actionInProgress, onExit,
|
|
260
|
+
}, [confirmationMode, actionInProgress, onExit, setConfirmationWithTimeout, resetConfirmation]));
|
|
541
261
|
const getMessageColor = (type) => {
|
|
542
262
|
switch (type) {
|
|
543
263
|
case "user":
|
|
@@ -589,13 +309,13 @@ const CodexDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal, title }
|
|
|
589
309
|
{
|
|
590
310
|
width: terminalWidth,
|
|
591
311
|
borderStyle: "round",
|
|
592
|
-
borderColor: actionInProgress ? "gray" : confirmationMode === "exit" ? "red" :
|
|
312
|
+
borderColor: actionInProgress ? "gray" : confirmationMode === "exit" ? "red" : "green",
|
|
593
313
|
paddingX: 2,
|
|
594
314
|
justifyContent: "center",
|
|
595
315
|
alignItems: "center",
|
|
596
316
|
flexDirection: "column"
|
|
597
317
|
},
|
|
598
|
-
/* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", alignItems: "center" }, actionInProgress === "exiting" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "Exiting agent...") :
|
|
318
|
+
/* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", alignItems: "center" }, actionInProgress === "exiting" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "Exiting agent...") : confirmationMode === "exit" ? /* @__PURE__ */ React.createElement(ink.Text, { color: "red", bold: true }, "\u26A0\uFE0F Press Ctrl-C again to exit the agent") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ink.Text, { color: "green", bold: true }, "\u{1F916} Codex Agent Running \u2022 Ctrl-C to exit")), process.env.DEBUG && logPath && /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Debug logs: ", logPath))
|
|
599
319
|
));
|
|
600
320
|
};
|
|
601
321
|
|
|
@@ -847,6 +567,152 @@ class ReasoningProcessor extends BaseReasoningProcessor.BaseReasoningProcessor {
|
|
|
847
567
|
}
|
|
848
568
|
}
|
|
849
569
|
|
|
570
|
+
class ConversationHistory {
|
|
571
|
+
messages = [];
|
|
572
|
+
maxMessages;
|
|
573
|
+
maxCharacters;
|
|
574
|
+
constructor(options = {}) {
|
|
575
|
+
this.maxMessages = options.maxMessages ?? 20;
|
|
576
|
+
this.maxCharacters = options.maxCharacters ?? 5e4;
|
|
577
|
+
}
|
|
578
|
+
isDuplicate(role, content) {
|
|
579
|
+
if (this.messages.length === 0) {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
for (let index = this.messages.length - 1; index >= 0; index -= 1) {
|
|
583
|
+
const message = this.messages[index];
|
|
584
|
+
if (message.role !== role) {
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
const normalizedIncoming = content.trim().replace(/\s+/g, " ");
|
|
588
|
+
const normalizedExisting = message.content.replace(/\s+/g, " ");
|
|
589
|
+
return normalizedIncoming === normalizedExisting;
|
|
590
|
+
}
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
addUserMessage(content) {
|
|
594
|
+
this.addMessage("user", content);
|
|
595
|
+
}
|
|
596
|
+
addAssistantMessage(content) {
|
|
597
|
+
this.addMessage("assistant", content);
|
|
598
|
+
}
|
|
599
|
+
hasHistory() {
|
|
600
|
+
return this.messages.length > 0;
|
|
601
|
+
}
|
|
602
|
+
size() {
|
|
603
|
+
return this.messages.length;
|
|
604
|
+
}
|
|
605
|
+
clear() {
|
|
606
|
+
this.messages = [];
|
|
607
|
+
api.logger.debug("[CodexConversationHistory] History cleared");
|
|
608
|
+
}
|
|
609
|
+
getContextForNewSession() {
|
|
610
|
+
if (this.messages.length === 0) {
|
|
611
|
+
return "";
|
|
612
|
+
}
|
|
613
|
+
const formattedMessages = this.messages.map((message) => {
|
|
614
|
+
const role = message.role === "user" ? "User" : "Assistant";
|
|
615
|
+
const content = message.content.length > 2e3 ? `${message.content.slice(0, 2e3)}... [truncated]` : message.content;
|
|
616
|
+
return `${role}: ${content}`;
|
|
617
|
+
}).join("\n\n");
|
|
618
|
+
return [
|
|
619
|
+
"[PREVIOUS CONVERSATION CONTEXT]",
|
|
620
|
+
"Continue from the prior Codex session using the conversation below as context.",
|
|
621
|
+
"",
|
|
622
|
+
formattedMessages,
|
|
623
|
+
"",
|
|
624
|
+
"[END OF PREVIOUS CONTEXT]",
|
|
625
|
+
""
|
|
626
|
+
].join("\n");
|
|
627
|
+
}
|
|
628
|
+
getSummary() {
|
|
629
|
+
const totalChars = this.messages.reduce((sum, message) => sum + message.content.length, 0);
|
|
630
|
+
const userCount = this.messages.filter((message) => message.role === "user").length;
|
|
631
|
+
const assistantCount = this.messages.filter((message) => message.role === "assistant").length;
|
|
632
|
+
return `${this.messages.length} messages (${userCount} user, ${assistantCount} assistant), ${totalChars} chars`;
|
|
633
|
+
}
|
|
634
|
+
addMessage(role, content) {
|
|
635
|
+
const trimmedContent = content.trim();
|
|
636
|
+
if (!trimmedContent) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (this.isDuplicate(role, trimmedContent)) {
|
|
640
|
+
api.logger.debug(`[CodexConversationHistory] Skipping duplicate ${role} message (${trimmedContent.length} chars)`);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
this.messages.push({
|
|
644
|
+
role,
|
|
645
|
+
content: trimmedContent,
|
|
646
|
+
timestamp: Date.now()
|
|
647
|
+
});
|
|
648
|
+
this.trimHistory();
|
|
649
|
+
api.logger.debug(`[CodexConversationHistory] Added ${role} message (${trimmedContent.length} chars), total: ${this.messages.length}`);
|
|
650
|
+
}
|
|
651
|
+
trimHistory() {
|
|
652
|
+
while (this.messages.length > this.maxMessages) {
|
|
653
|
+
this.messages.shift();
|
|
654
|
+
}
|
|
655
|
+
let totalChars = this.messages.reduce((sum, message) => sum + message.content.length, 0);
|
|
656
|
+
while (totalChars > this.maxCharacters && this.messages.length > 1) {
|
|
657
|
+
const removed = this.messages.shift();
|
|
658
|
+
if (removed) {
|
|
659
|
+
totalChars -= removed.content.length;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function resolveCodexAcpExecutionMode(mode) {
|
|
666
|
+
const approvalPolicy = (() => {
|
|
667
|
+
switch (mode.permissionMode) {
|
|
668
|
+
case "default":
|
|
669
|
+
return "on-request";
|
|
670
|
+
case "read-only":
|
|
671
|
+
return "on-request";
|
|
672
|
+
case "safe-yolo":
|
|
673
|
+
return "on-request";
|
|
674
|
+
case "yolo":
|
|
675
|
+
return "never";
|
|
676
|
+
case "bypassPermissions":
|
|
677
|
+
return "never";
|
|
678
|
+
case "acceptEdits":
|
|
679
|
+
return "on-request";
|
|
680
|
+
case "plan":
|
|
681
|
+
return "on-request";
|
|
682
|
+
default:
|
|
683
|
+
return "on-request";
|
|
684
|
+
}
|
|
685
|
+
})();
|
|
686
|
+
const sandbox = (() => {
|
|
687
|
+
switch (mode.permissionMode) {
|
|
688
|
+
case "default":
|
|
689
|
+
return "workspace-write";
|
|
690
|
+
case "read-only":
|
|
691
|
+
return "read-only";
|
|
692
|
+
case "safe-yolo":
|
|
693
|
+
return "workspace-write";
|
|
694
|
+
case "yolo":
|
|
695
|
+
return "danger-full-access";
|
|
696
|
+
case "bypassPermissions":
|
|
697
|
+
return "danger-full-access";
|
|
698
|
+
case "acceptEdits":
|
|
699
|
+
return "workspace-write";
|
|
700
|
+
case "plan":
|
|
701
|
+
return "read-only";
|
|
702
|
+
default:
|
|
703
|
+
return "read-only";
|
|
704
|
+
}
|
|
705
|
+
})();
|
|
706
|
+
return {
|
|
707
|
+
approvalPolicy,
|
|
708
|
+
sandbox,
|
|
709
|
+
model: mode.model
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
function getCodexExecutionFingerprint(mode) {
|
|
713
|
+
return registerKillSessionHandler.hashObject(resolveCodexAcpExecutionMode(mode));
|
|
714
|
+
}
|
|
715
|
+
|
|
850
716
|
function createAbortError() {
|
|
851
717
|
const error = new Error("Operation aborted");
|
|
852
718
|
error.name = "AbortError";
|
|
@@ -899,6 +765,9 @@ function normalizeCodexBackendError(error) {
|
|
|
899
765
|
}
|
|
900
766
|
return text || "Codex backend exited unexpectedly";
|
|
901
767
|
}
|
|
768
|
+
function getCodexLegacySwitchIgnoredMessage() {
|
|
769
|
+
return "Codex now runs in ACP-only mode. Staying in the current session.";
|
|
770
|
+
}
|
|
902
771
|
function shouldEmitCodexReadyAfterWait(opts) {
|
|
903
772
|
if (opts.readyAlreadySent) {
|
|
904
773
|
return false;
|
|
@@ -925,58 +794,10 @@ function handleIncomingCodexMessageDuringRemoteTurn(opts) {
|
|
|
925
794
|
}
|
|
926
795
|
return total > 0 || interruptedTurn;
|
|
927
796
|
}
|
|
928
|
-
function resolveCodexAcpExecutionMode(mode) {
|
|
929
|
-
const approvalPolicy = (() => {
|
|
930
|
-
switch (mode.permissionMode) {
|
|
931
|
-
case "default":
|
|
932
|
-
return "on-request";
|
|
933
|
-
case "read-only":
|
|
934
|
-
return "on-request";
|
|
935
|
-
case "safe-yolo":
|
|
936
|
-
return "on-request";
|
|
937
|
-
case "yolo":
|
|
938
|
-
return "never";
|
|
939
|
-
case "bypassPermissions":
|
|
940
|
-
return "never";
|
|
941
|
-
case "acceptEdits":
|
|
942
|
-
return "on-request";
|
|
943
|
-
case "plan":
|
|
944
|
-
return "on-request";
|
|
945
|
-
default:
|
|
946
|
-
return "on-request";
|
|
947
|
-
}
|
|
948
|
-
})();
|
|
949
|
-
const sandbox = (() => {
|
|
950
|
-
switch (mode.permissionMode) {
|
|
951
|
-
case "default":
|
|
952
|
-
return "workspace-write";
|
|
953
|
-
case "read-only":
|
|
954
|
-
return "read-only";
|
|
955
|
-
case "safe-yolo":
|
|
956
|
-
return "workspace-write";
|
|
957
|
-
case "yolo":
|
|
958
|
-
return "danger-full-access";
|
|
959
|
-
case "bypassPermissions":
|
|
960
|
-
return "danger-full-access";
|
|
961
|
-
case "acceptEdits":
|
|
962
|
-
return "workspace-write";
|
|
963
|
-
case "plan":
|
|
964
|
-
return "read-only";
|
|
965
|
-
default:
|
|
966
|
-
return "read-only";
|
|
967
|
-
}
|
|
968
|
-
})();
|
|
969
|
-
return {
|
|
970
|
-
approvalPolicy,
|
|
971
|
-
sandbox,
|
|
972
|
-
model: mode.model
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
797
|
async function codexRemoteLauncher(session) {
|
|
976
798
|
const messageBuffer = new registerKillSessionHandler.MessageBuffer();
|
|
977
799
|
const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
|
|
978
800
|
let inkInstance = null;
|
|
979
|
-
let exitReason = null;
|
|
980
801
|
let shouldExit = false;
|
|
981
802
|
let abortController = new AbortController();
|
|
982
803
|
let turnInFlight = false;
|
|
@@ -989,8 +810,10 @@ async function codexRemoteLauncher(session) {
|
|
|
989
810
|
let accumulatedResponse = "";
|
|
990
811
|
let isResponseInProgress = false;
|
|
991
812
|
let taskStartedSent = false;
|
|
813
|
+
let shouldInjectHistoryOnNextSession = false;
|
|
992
814
|
const permissionHandler = new CodexPermissionHandler(session.client);
|
|
993
815
|
const selectionHandler = new CodexSelectionHandler(session.client);
|
|
816
|
+
const conversationHistory = new ConversationHistory({ maxMessages: 20, maxCharacters: 5e4 });
|
|
994
817
|
const reasoningProcessor = new ReasoningProcessor((message) => {
|
|
995
818
|
session.runtimeSession.sendCodexMessage(message);
|
|
996
819
|
});
|
|
@@ -1043,10 +866,26 @@ async function codexRemoteLauncher(session) {
|
|
|
1043
866
|
api.logger.debug("[Codex] Error disposing ACP backend:", error);
|
|
1044
867
|
}
|
|
1045
868
|
};
|
|
869
|
+
const emitStatusMessage = (message) => {
|
|
870
|
+
messageBuffer.addMessage(message, "status");
|
|
871
|
+
session.runtimeSession.sendSessionEvent({ type: "message", message });
|
|
872
|
+
};
|
|
873
|
+
const queueHistoryInjectionForRestart = (reason) => {
|
|
874
|
+
messageBuffer.addMessage("\u2550".repeat(40), "status");
|
|
875
|
+
if (conversationHistory.hasHistory()) {
|
|
876
|
+
shouldInjectHistoryOnNextSession = true;
|
|
877
|
+
const message = `${reason} Preserving ${conversationHistory.size()} earlier messages of context.`;
|
|
878
|
+
emitStatusMessage(message);
|
|
879
|
+
api.logger.debug(`[Codex] Will inject conversation history after restart: ${conversationHistory.getSummary()}`);
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
emitStatusMessage(reason);
|
|
883
|
+
};
|
|
1046
884
|
const emitFinalAssistantMessage = () => {
|
|
1047
885
|
if (!accumulatedResponse.trim()) {
|
|
1048
886
|
return;
|
|
1049
887
|
}
|
|
888
|
+
conversationHistory.addAssistantMessage(accumulatedResponse);
|
|
1050
889
|
session.runtimeSession.sendCodexMessage({
|
|
1051
890
|
type: "message",
|
|
1052
891
|
message: accumulatedResponse,
|
|
@@ -1261,11 +1100,9 @@ async function codexRemoteLauncher(session) {
|
|
|
1261
1100
|
return result.backend;
|
|
1262
1101
|
};
|
|
1263
1102
|
const handleSwitchToLocal = async () => {
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
shouldExit = true;
|
|
1268
|
-
await handleAbort();
|
|
1103
|
+
const message = getCodexLegacySwitchIgnoredMessage();
|
|
1104
|
+
api.logger.debug("[Codex] Ignoring legacy switch request because Codex is ACP-only");
|
|
1105
|
+
emitStatusMessage(message);
|
|
1269
1106
|
};
|
|
1270
1107
|
session.setPendingInteractionSuperseder((reason = BaseReasoningProcessor.INTERACTION_SUPERSEDED_ERROR) => {
|
|
1271
1108
|
return handleIncomingCodexMessageDuringRemoteTurn({
|
|
@@ -1326,17 +1163,9 @@ async function codexRemoteLauncher(session) {
|
|
|
1326
1163
|
inkInstance = ink.render(React.createElement(CodexDisplay, {
|
|
1327
1164
|
messageBuffer,
|
|
1328
1165
|
logPath: process.env.DEBUG ? session.logPath : void 0,
|
|
1329
|
-
title: "
|
|
1166
|
+
title: "Codex Agent Messages",
|
|
1330
1167
|
onExit: async () => {
|
|
1331
|
-
api.logger.debug("[Codex]
|
|
1332
|
-
exitReason = "exit";
|
|
1333
|
-
shouldExit = true;
|
|
1334
|
-
await handleAbort();
|
|
1335
|
-
},
|
|
1336
|
-
onSwitchToLocal: async () => {
|
|
1337
|
-
api.logger.debug("[Codex] Switching to local from local keyboard");
|
|
1338
|
-
session.pinLocalMode();
|
|
1339
|
-
exitReason = "switch";
|
|
1168
|
+
api.logger.debug("[Codex] Exiting Codex ACP session from keyboard");
|
|
1340
1169
|
shouldExit = true;
|
|
1341
1170
|
await handleAbort();
|
|
1342
1171
|
}
|
|
@@ -1376,8 +1205,7 @@ async function codexRemoteLauncher(session) {
|
|
|
1376
1205
|
break;
|
|
1377
1206
|
}
|
|
1378
1207
|
if (wasSessionCreated && currentModeHash && message.hash !== currentModeHash) {
|
|
1379
|
-
|
|
1380
|
-
messageBuffer.addMessage("Starting new Codex session (mode changed)...", "status");
|
|
1208
|
+
queueHistoryInjectionForRestart("Starting new Codex session (execution settings changed)...");
|
|
1381
1209
|
await disposeBackend();
|
|
1382
1210
|
session.clearSessionId();
|
|
1383
1211
|
currentModeHash = null;
|
|
@@ -1404,27 +1232,33 @@ async function codexRemoteLauncher(session) {
|
|
|
1404
1232
|
session.onSessionFound(sessionId);
|
|
1405
1233
|
}
|
|
1406
1234
|
session.onThinkingChange(true);
|
|
1407
|
-
|
|
1235
|
+
let promptToSend = message.message;
|
|
1236
|
+
if (shouldInjectHistoryOnNextSession && conversationHistory.hasHistory()) {
|
|
1237
|
+
const historyContext = conversationHistory.getContextForNewSession();
|
|
1238
|
+
promptToSend = historyContext + promptToSend;
|
|
1239
|
+
api.logger.debug(`[Codex] Injected conversation history context (${historyContext.length} chars)`);
|
|
1240
|
+
}
|
|
1241
|
+
conversationHistory.addUserMessage(message.message);
|
|
1242
|
+
await activeBackend.sendPrompt(acpSessionId, promptToSend);
|
|
1408
1243
|
await waitForResponseCompleteWithAbort(activeBackend, turnSignal);
|
|
1409
1244
|
reasoningProcessor.completeCurrent();
|
|
1245
|
+
shouldInjectHistoryOnNextSession = false;
|
|
1410
1246
|
} catch (error) {
|
|
1411
1247
|
api.logger.warn("Error in codex ACP session:", error);
|
|
1412
1248
|
const isAbortError = error instanceof Error && error.name === "AbortError";
|
|
1413
|
-
const isExpectedInterruption = isAbortError || turnSignal.aborted || shouldExit
|
|
1414
|
-
if (
|
|
1415
|
-
messageBuffer.addMessage("Switching to local mode...", "status");
|
|
1416
|
-
session.runtimeSession.sendSessionEvent({ type: "message", message: "Switching to local mode..." });
|
|
1417
|
-
} else if (isExpectedInterruption) {
|
|
1249
|
+
const isExpectedInterruption = isAbortError || turnSignal.aborted || shouldExit;
|
|
1250
|
+
if (isExpectedInterruption) {
|
|
1418
1251
|
session.runtimeSession.sendCodexMessage({
|
|
1419
1252
|
type: "turn_aborted",
|
|
1420
1253
|
id: node_crypto.randomUUID()
|
|
1421
1254
|
});
|
|
1422
|
-
|
|
1423
|
-
session.runtimeSession.sendSessionEvent({ type: "message", message: "Aborted by user" });
|
|
1255
|
+
emitStatusMessage("Aborted by user");
|
|
1424
1256
|
} else {
|
|
1425
1257
|
const errorMessage = normalizeCodexBackendError(error);
|
|
1426
|
-
|
|
1427
|
-
|
|
1258
|
+
emitStatusMessage(errorMessage);
|
|
1259
|
+
if (conversationHistory.hasHistory()) {
|
|
1260
|
+
shouldInjectHistoryOnNextSession = true;
|
|
1261
|
+
}
|
|
1428
1262
|
await disposeBackend();
|
|
1429
1263
|
session.clearSessionId();
|
|
1430
1264
|
}
|
|
@@ -1478,33 +1312,16 @@ async function codexRemoteLauncher(session) {
|
|
|
1478
1312
|
}
|
|
1479
1313
|
messageBuffer.clear();
|
|
1480
1314
|
}
|
|
1481
|
-
api.logger.debug(
|
|
1482
|
-
return
|
|
1315
|
+
api.logger.debug("[Codex] ACP remote launcher returning: exit");
|
|
1316
|
+
return "exit";
|
|
1483
1317
|
}
|
|
1484
1318
|
|
|
1485
1319
|
async function codexLoop(opts) {
|
|
1486
|
-
|
|
1487
|
-
await opts.session.onModeChange(
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
const result = await codexLocalLauncher(opts.session);
|
|
1492
|
-
if (result.type === "switch") {
|
|
1493
|
-
opts.session.clearLocalModePin();
|
|
1494
|
-
mode = "remote";
|
|
1495
|
-
await opts.session.onModeChange(mode);
|
|
1496
|
-
continue;
|
|
1497
|
-
}
|
|
1498
|
-
return result.code;
|
|
1499
|
-
}
|
|
1500
|
-
const reason = await codexRemoteLauncher(opts.session);
|
|
1501
|
-
if (reason === "switch") {
|
|
1502
|
-
mode = "local";
|
|
1503
|
-
await opts.session.onModeChange(mode);
|
|
1504
|
-
continue;
|
|
1505
|
-
}
|
|
1506
|
-
return 0;
|
|
1507
|
-
}
|
|
1320
|
+
const displayMode = opts.startingMode ?? "local";
|
|
1321
|
+
await opts.session.onModeChange(displayMode);
|
|
1322
|
+
api.logger.debug(`[codex-loop] Starting ACP-only Codex launcher with display mode: ${displayMode}`);
|
|
1323
|
+
await codexRemoteLauncher(opts.session);
|
|
1324
|
+
return 0;
|
|
1508
1325
|
}
|
|
1509
1326
|
|
|
1510
1327
|
function supportsAgentStateUpdateEvents(sessionClient) {
|
|
@@ -1545,16 +1362,17 @@ async function syncControlledByUserState(sessionClient, controlledByUser) {
|
|
|
1545
1362
|
});
|
|
1546
1363
|
}
|
|
1547
1364
|
function shouldSupersedeCodexPendingInteractions(opts) {
|
|
1548
|
-
return
|
|
1365
|
+
return true;
|
|
1549
1366
|
}
|
|
1550
1367
|
async function runCodex(opts) {
|
|
1551
1368
|
const sessionTag = node_crypto.randomUUID();
|
|
1552
1369
|
api.connectionState.setBackend("Codex");
|
|
1553
|
-
|
|
1370
|
+
const requestedStartingMode = opts.startingMode ?? (opts.startedBy === "daemon" ? "remote" : "local");
|
|
1371
|
+
if (opts.startedBy === "daemon" && requestedStartingMode === "local") {
|
|
1554
1372
|
throw new Error("Daemon-spawned Codex sessions cannot use local mode.");
|
|
1555
1373
|
}
|
|
1556
1374
|
const api$1 = await api.ApiClient.create(opts.credentials);
|
|
1557
|
-
api.logger.debug(`[codex] Starting with options: startedBy=${opts.startedBy || "terminal"},
|
|
1375
|
+
api.logger.debug(`[codex] Starting with options: startedBy=${opts.startedBy || "terminal"}, requestedStartingMode=${requestedStartingMode}, executionMode=acp-only`);
|
|
1558
1376
|
const settings = await persistence.readSettings();
|
|
1559
1377
|
const machineId = settings?.machineId;
|
|
1560
1378
|
if (!machineId) {
|
|
@@ -1603,10 +1421,7 @@ async function runCodex(opts) {
|
|
|
1603
1421
|
api.logger.debug("[START] Failed to report to daemon:", error);
|
|
1604
1422
|
}
|
|
1605
1423
|
}
|
|
1606
|
-
const messageQueue = new registerKillSessionHandler.MessageQueue2(
|
|
1607
|
-
permissionMode: mode.permissionMode,
|
|
1608
|
-
model: mode.model
|
|
1609
|
-
}));
|
|
1424
|
+
const messageQueue = new registerKillSessionHandler.MessageQueue2(getCodexExecutionFingerprint);
|
|
1610
1425
|
let currentPermissionMode;
|
|
1611
1426
|
let currentModel;
|
|
1612
1427
|
sessionClient.onUserMessage((message) => {
|
|
@@ -1638,7 +1453,7 @@ async function runCodex(opts) {
|
|
|
1638
1453
|
path: process.cwd(),
|
|
1639
1454
|
logPath: api.logger.logFilePath,
|
|
1640
1455
|
sessionId: null,
|
|
1641
|
-
mode:
|
|
1456
|
+
mode: requestedStartingMode,
|
|
1642
1457
|
messageQueue,
|
|
1643
1458
|
codexArgs: opts.codexArgs,
|
|
1644
1459
|
onModeChange: async (mode) => {
|
|
@@ -1649,7 +1464,7 @@ async function runCodex(opts) {
|
|
|
1649
1464
|
try {
|
|
1650
1465
|
const exitCode = await codexLoop({
|
|
1651
1466
|
session: codexSession,
|
|
1652
|
-
startingMode:
|
|
1467
|
+
startingMode: requestedStartingMode
|
|
1653
1468
|
});
|
|
1654
1469
|
sessionClient.sendSessionDeath();
|
|
1655
1470
|
await sessionClient.flush();
|