happy-imou-cloud 2.0.0 → 2.0.2
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-BRCQXCZY.cjs → BaseReasoningProcessor-B6tJ_eL5.cjs} +96 -9
- package/dist/{BaseReasoningProcessor-BKLRCKTU.mjs → BaseReasoningProcessor-D8VhEbs2.mjs} +95 -10
- package/dist/{api-D7OK-mML.cjs → api-D2Njw9Im.cjs} +124 -6
- package/dist/{api-BGXYX0yH.mjs → api-MYhAGPLn.mjs} +122 -7
- package/dist/{command-G85giEAF.cjs → command-CVldr51S.cjs} +3 -3
- package/dist/{command-CnLtKtP-.mjs → command-nmK6O-ab.mjs} +3 -3
- package/dist/{index-C7Y0R-MI.mjs → index-B97L7qLD.mjs} +689 -229
- package/dist/{index-B_wlQBy2.cjs → index-Bg-YziG2.cjs} +691 -229
- package/dist/index.cjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +7 -0
- package/dist/lib.d.mts +7 -0
- package/dist/lib.mjs +1 -1
- package/dist/{persistence-DHgf1CTG.cjs → persistence-D_2GkJAO.cjs} +28 -6
- package/dist/{persistence-BA_unuca.mjs → persistence-Dkm7rm8k.mjs} +29 -7
- package/dist/{registerKillSessionHandler-C2-yHm1V.mjs → registerKillSessionHandler-5GbrO0FM.mjs} +6 -4
- package/dist/{registerKillSessionHandler-CLREXN11.cjs → registerKillSessionHandler-BAXmJQRt.cjs} +6 -4
- package/dist/{runClaude-CwAitpX-.cjs → runClaude-B-GNEkKg.cjs} +237 -45
- package/dist/{runClaude-uNC5Eym4.mjs → runClaude-Cii3R2Fv.mjs} +238 -46
- package/dist/{runCodex-B-05E-YZ.mjs → runCodex-C--ZwAhl.mjs} +636 -819
- package/dist/{runCodex-Cm0VTqw_.cjs → runCodex-CPHyGwj9.cjs} +639 -819
- package/dist/{runGemini-_biXvQAH.mjs → runGemini-CQp7Nuzn.mjs} +20 -16
- package/dist/{runGemini-CLWjwDYS.cjs → runGemini-DaDz1bzQ.cjs} +20 -16
- package/package.json +14 -15
- package/scripts/env-wrapper.cjs +11 -11
- package/scripts/setup-dev.cjs +4 -4
|
@@ -1,11 +1,11 @@
|
|
|
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-
|
|
4
|
+
var api = require('./api-D2Njw9Im.cjs');
|
|
5
|
+
var persistence = require('./persistence-D_2GkJAO.cjs');
|
|
6
|
+
var index = require('./index-Bg-YziG2.cjs');
|
|
7
|
+
var BaseReasoningProcessor = require('./BaseReasoningProcessor-B6tJ_eL5.cjs');
|
|
8
|
+
var registerKillSessionHandler = require('./registerKillSessionHandler-BAXmJQRt.cjs');
|
|
9
9
|
var future = require('./future-Dq4Ha1Dn.cjs');
|
|
10
10
|
var node_child_process = require('node:child_process');
|
|
11
11
|
var fs = require('node:fs');
|
|
@@ -13,17 +13,14 @@ var os = require('node:os');
|
|
|
13
13
|
var path = require('node:path');
|
|
14
14
|
var React = require('react');
|
|
15
15
|
var ink = require('ink');
|
|
16
|
-
var index_js = require('@modelcontextprotocol/sdk/client/index.js');
|
|
17
|
-
var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
18
|
-
var z = require('zod');
|
|
19
|
-
var types_js = require('@modelcontextprotocol/sdk/types.js');
|
|
20
|
-
var child_process = require('child_process');
|
|
21
16
|
require('axios');
|
|
22
17
|
require('chalk');
|
|
23
18
|
require('fs');
|
|
24
19
|
require('node:events');
|
|
25
20
|
require('socket.io-client');
|
|
21
|
+
require('zod');
|
|
26
22
|
require('tweetnacl');
|
|
23
|
+
require('child_process');
|
|
27
24
|
require('util');
|
|
28
25
|
require('fs/promises');
|
|
29
26
|
require('crypto');
|
|
@@ -133,6 +130,7 @@ class CodexSession {
|
|
|
133
130
|
sessionId;
|
|
134
131
|
mode;
|
|
135
132
|
thinking = false;
|
|
133
|
+
localModePinned = false;
|
|
136
134
|
keepAliveInterval;
|
|
137
135
|
onModeChangeCallback;
|
|
138
136
|
clientSwapCallbacks = [];
|
|
@@ -187,11 +185,23 @@ class CodexSession {
|
|
|
187
185
|
this.thinking = thinking;
|
|
188
186
|
this.client.keepAlive(thinking, this.mode);
|
|
189
187
|
};
|
|
190
|
-
onModeChange = (mode) => {
|
|
188
|
+
onModeChange = async (mode) => {
|
|
191
189
|
this.mode = mode;
|
|
192
190
|
this.client.keepAlive(this.thinking, mode);
|
|
193
|
-
this.onModeChangeCallback(mode);
|
|
191
|
+
await this.onModeChangeCallback(mode);
|
|
194
192
|
};
|
|
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;
|
|
195
205
|
onSessionFound = (sessionId) => {
|
|
196
206
|
if (this.sessionId === sessionId) {
|
|
197
207
|
return;
|
|
@@ -234,21 +244,6 @@ function extractCodexSessionIdFromPath(filePath) {
|
|
|
234
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$/);
|
|
235
245
|
return match?.[1] ?? null;
|
|
236
246
|
}
|
|
237
|
-
function findCodexResumeFile(sessionId) {
|
|
238
|
-
if (!sessionId) return null;
|
|
239
|
-
try {
|
|
240
|
-
const candidates = collectFilesRecursive(getCodexSessionsRoot()).filter((full) => full.endsWith(`-${sessionId}.jsonl`)).filter((full) => {
|
|
241
|
-
try {
|
|
242
|
-
return fs.statSync(full).isFile();
|
|
243
|
-
} catch {
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
}).sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
|
|
247
|
-
return candidates[0] || null;
|
|
248
|
-
} catch {
|
|
249
|
-
return null;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
247
|
function findLatestCodexSessionIdSince(startTimeMs) {
|
|
253
248
|
try {
|
|
254
249
|
const candidates = collectFilesRecursive(getCodexSessionsRoot()).filter((full) => full.endsWith(".jsonl")).filter((full) => {
|
|
@@ -270,6 +265,36 @@ function findLatestCodexSessionIdSince(startTimeMs) {
|
|
|
270
265
|
return null;
|
|
271
266
|
}
|
|
272
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
|
+
|
|
273
298
|
class CodexExitCodeError extends Error {
|
|
274
299
|
exitCode;
|
|
275
300
|
constructor(exitCode) {
|
|
@@ -297,7 +322,7 @@ async function codexLocal(opts) {
|
|
|
297
322
|
const args = opts.sessionId ? ["resume", opts.sessionId, ...baseArgs] : baseArgs;
|
|
298
323
|
const startTime = Date.now();
|
|
299
324
|
let detectedSessionId = opts.sessionId;
|
|
300
|
-
const codexExecutable =
|
|
325
|
+
const codexExecutable = resolveCodexExecutable();
|
|
301
326
|
api.logger.debug(`[CodexLocal] Spawning ${codexExecutable} with args: ${JSON.stringify(args)}`);
|
|
302
327
|
process.stdin.pause();
|
|
303
328
|
try {
|
|
@@ -307,7 +332,7 @@ async function codexLocal(opts) {
|
|
|
307
332
|
env: process.env,
|
|
308
333
|
stdio: "inherit",
|
|
309
334
|
signal: opts.abort,
|
|
310
|
-
shell:
|
|
335
|
+
shell: shouldUseShellForCodex(codexExecutable)
|
|
311
336
|
});
|
|
312
337
|
const abortChild = () => {
|
|
313
338
|
if (child.exitCode === null) {
|
|
@@ -387,10 +412,18 @@ async function codexLocalLauncher(session) {
|
|
|
387
412
|
session.client.rpcHandlerManager.registerHandler("abort", doAbort);
|
|
388
413
|
session.client.rpcHandlerManager.registerHandler("switch", doSwitch);
|
|
389
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
|
+
}
|
|
390
419
|
void doSwitch();
|
|
391
420
|
});
|
|
392
421
|
if (session.queue.size() > 0) {
|
|
393
|
-
|
|
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
|
+
}
|
|
394
427
|
}
|
|
395
428
|
while (true) {
|
|
396
429
|
if (exitReason) {
|
|
@@ -524,7 +557,7 @@ const CodexDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal, title }
|
|
|
524
557
|
}
|
|
525
558
|
};
|
|
526
559
|
const formatMessage = (msg) => {
|
|
527
|
-
const lines = msg.content.split("\n");
|
|
560
|
+
const lines = index.formatDisplayMessage(msg.content).split("\n");
|
|
528
561
|
const maxLineLength = terminalWidth - 10;
|
|
529
562
|
return lines.map((line) => {
|
|
530
563
|
if (line.length <= maxLineLength) return line;
|
|
@@ -566,462 +599,6 @@ const CodexDisplay = ({ messageBuffer, logPath, onExit, onSwitchToLocal, title }
|
|
|
566
599
|
));
|
|
567
600
|
};
|
|
568
601
|
|
|
569
|
-
const DEFAULT_TIMEOUT = 14 * 24 * 60 * 60 * 1e3;
|
|
570
|
-
function getCodexMcpCommand() {
|
|
571
|
-
const codexExecutable = index.resolveCodexExecutable();
|
|
572
|
-
try {
|
|
573
|
-
const version = child_process.execSync(`"${codexExecutable}" --version`, { encoding: "utf8" }).trim();
|
|
574
|
-
const match = version.match(/codex-cli\s+(\d+\.\d+\.\d+(?:-alpha\.\d+)?)/);
|
|
575
|
-
if (!match) {
|
|
576
|
-
api.logger.debug("[CodexMCP] Could not parse codex version:", version);
|
|
577
|
-
return null;
|
|
578
|
-
}
|
|
579
|
-
const versionStr = match[1];
|
|
580
|
-
const [major, minor, patch] = versionStr.split(/[-.]/).map(Number);
|
|
581
|
-
if (major > 0 || minor > 43) return "mcp-server";
|
|
582
|
-
if (minor === 43 && patch === 0) {
|
|
583
|
-
if (versionStr.includes("-alpha.")) {
|
|
584
|
-
const alphaNum = parseInt(versionStr.split("-alpha.")[1]);
|
|
585
|
-
return alphaNum >= 5 ? "mcp-server" : "mcp";
|
|
586
|
-
}
|
|
587
|
-
return "mcp-server";
|
|
588
|
-
}
|
|
589
|
-
return "mcp";
|
|
590
|
-
} catch (error) {
|
|
591
|
-
api.logger.debug("[CodexMCP] Codex CLI not found or not executable:", error);
|
|
592
|
-
return null;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
function resolveCodexMcpTransport(executable, mcpCommand) {
|
|
596
|
-
return {
|
|
597
|
-
// Preserve the resolved executable path so daemon/service environments do not
|
|
598
|
-
// depend on APPDATA npm shims being present on PATH.
|
|
599
|
-
command: executable,
|
|
600
|
-
args: [mcpCommand]
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
function extractCodexIdentifiers(response) {
|
|
604
|
-
const meta = response?.meta || {};
|
|
605
|
-
const structured = response?.structuredContent || response?.structured_content || response?.structured_output || {};
|
|
606
|
-
const sessionId = meta.sessionId || meta.session_id || response?.sessionId || response?.session_id || meta.threadId || meta.thread_id || response?.threadId || response?.thread_id || structured.threadId || structured.thread_id || null;
|
|
607
|
-
const conversationId = meta.conversationId || meta.conversation_id || response?.conversationId || response?.conversation_id || meta.threadId || meta.thread_id || response?.threadId || response?.thread_id || structured.threadId || structured.thread_id || structured.conversationId || structured.conversation_id || null;
|
|
608
|
-
return { sessionId, conversationId };
|
|
609
|
-
}
|
|
610
|
-
function normalizeCodexPermissionRequest(params) {
|
|
611
|
-
return {
|
|
612
|
-
requestId: params.codex_call_id || params.call_id || params.codex_mcp_tool_call_id || params.codex_event_id || `codex-permission-${Date.now()}`,
|
|
613
|
-
toolName: "CodexBash",
|
|
614
|
-
input: {
|
|
615
|
-
command: params.codex_command || params.command,
|
|
616
|
-
cwd: params.codex_cwd || params.cwd,
|
|
617
|
-
proposed_execpolicy_amendment: params.proposed_execpolicy_amendment,
|
|
618
|
-
parsed_cmd: params.parsed_cmd
|
|
619
|
-
}
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
function resolveCodexPermissionRequest(params, fallback) {
|
|
623
|
-
const normalized = normalizeCodexPermissionRequest(params);
|
|
624
|
-
const hasInlinePayload = Boolean(
|
|
625
|
-
normalized.input.command || normalized.input.cwd || normalized.input.proposed_execpolicy_amendment || normalized.input.parsed_cmd
|
|
626
|
-
);
|
|
627
|
-
const hasInlineId = Boolean(
|
|
628
|
-
params.codex_call_id || params.call_id || params.codex_mcp_tool_call_id || params.codex_event_id
|
|
629
|
-
);
|
|
630
|
-
if (!fallback || hasInlinePayload && hasInlineId) {
|
|
631
|
-
return normalized;
|
|
632
|
-
}
|
|
633
|
-
return {
|
|
634
|
-
requestId: hasInlineId ? normalized.requestId : fallback.call_id || normalized.requestId,
|
|
635
|
-
toolName: normalized.toolName,
|
|
636
|
-
input: {
|
|
637
|
-
command: normalized.input.command || fallback.command,
|
|
638
|
-
cwd: normalized.input.cwd || fallback.cwd,
|
|
639
|
-
proposed_execpolicy_amendment: normalized.input.proposed_execpolicy_amendment || fallback.proposed_execpolicy_amendment,
|
|
640
|
-
parsed_cmd: normalized.input.parsed_cmd || fallback.parsed_cmd
|
|
641
|
-
}
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
class CodexMcpClient {
|
|
645
|
-
client;
|
|
646
|
-
transport = null;
|
|
647
|
-
connected = false;
|
|
648
|
-
sessionId = null;
|
|
649
|
-
conversationId = null;
|
|
650
|
-
handler = null;
|
|
651
|
-
permissionHandler = null;
|
|
652
|
-
selectionHandler = null;
|
|
653
|
-
pendingExecApprovalContexts = [];
|
|
654
|
-
constructor() {
|
|
655
|
-
this.client = this.createClient();
|
|
656
|
-
}
|
|
657
|
-
createClient() {
|
|
658
|
-
const client = new index_js.Client(
|
|
659
|
-
{ name: "happy-codex-client", version: "1.0.0" },
|
|
660
|
-
{ capabilities: { elicitation: {} } }
|
|
661
|
-
);
|
|
662
|
-
client.setNotificationHandler(z.z.object({
|
|
663
|
-
method: z.z.literal("codex/event"),
|
|
664
|
-
params: z.z.object({
|
|
665
|
-
msg: z.z.any()
|
|
666
|
-
})
|
|
667
|
-
}).passthrough(), (data) => {
|
|
668
|
-
const msg = data.params.msg;
|
|
669
|
-
if (msg?.type === "exec_approval_request") {
|
|
670
|
-
this.pendingExecApprovalContexts.push({
|
|
671
|
-
call_id: msg.call_id,
|
|
672
|
-
command: msg.command,
|
|
673
|
-
cwd: msg.cwd,
|
|
674
|
-
proposed_execpolicy_amendment: msg.proposed_execpolicy_amendment,
|
|
675
|
-
parsed_cmd: msg.parsed_cmd
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
this.updateIdentifiersFromEvent(msg);
|
|
679
|
-
this.handler?.(msg);
|
|
680
|
-
});
|
|
681
|
-
return client;
|
|
682
|
-
}
|
|
683
|
-
setHandler(handler) {
|
|
684
|
-
this.handler = handler;
|
|
685
|
-
}
|
|
686
|
-
/**
|
|
687
|
-
* Set the permission handler for tool approval
|
|
688
|
-
*/
|
|
689
|
-
setPermissionHandler(handler) {
|
|
690
|
-
this.permissionHandler = handler;
|
|
691
|
-
}
|
|
692
|
-
setSelectionHandler(handler) {
|
|
693
|
-
this.selectionHandler = handler;
|
|
694
|
-
}
|
|
695
|
-
async connect() {
|
|
696
|
-
if (this.connected) return;
|
|
697
|
-
const mcpCommand = getCodexMcpCommand();
|
|
698
|
-
if (mcpCommand === null) {
|
|
699
|
-
throw new Error(
|
|
700
|
-
"Codex CLI not found or not executable.\n\nTo install codex:\n npm install -g @openai/codex\n\nAlternatively, use Claude:\n happy claude"
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
api.logger.debug(`[CodexMCP] Connecting to Codex MCP server using command: codex ${mcpCommand}`);
|
|
704
|
-
const codexExecutable = index.resolveCodexExecutable();
|
|
705
|
-
const transport = resolveCodexMcpTransport(codexExecutable, mcpCommand);
|
|
706
|
-
this.transport = new stdio_js.StdioClientTransport({
|
|
707
|
-
command: transport.command,
|
|
708
|
-
args: transport.args,
|
|
709
|
-
env: Object.keys(process.env).reduce((acc, key) => {
|
|
710
|
-
const value = process.env[key];
|
|
711
|
-
if (typeof value === "string") acc[key] = value;
|
|
712
|
-
return acc;
|
|
713
|
-
}, {})
|
|
714
|
-
});
|
|
715
|
-
this.registerPermissionHandlers();
|
|
716
|
-
await this.client.connect(this.transport);
|
|
717
|
-
this.connected = true;
|
|
718
|
-
api.logger.debug("[CodexMCP] Connected to Codex");
|
|
719
|
-
}
|
|
720
|
-
isClosedTransportError(error) {
|
|
721
|
-
const record = error;
|
|
722
|
-
const code = typeof record?.code === "number" ? record.code : null;
|
|
723
|
-
if (code === -32e3) {
|
|
724
|
-
return true;
|
|
725
|
-
}
|
|
726
|
-
const message = typeof record?.message === "string" ? record.message.toLowerCase() : "";
|
|
727
|
-
if (code === -32001 && message.includes("aborterror")) {
|
|
728
|
-
return true;
|
|
729
|
-
}
|
|
730
|
-
return message.includes("connection closed") || message.includes("not connected");
|
|
731
|
-
}
|
|
732
|
-
async callToolWithReconnectRetry(call) {
|
|
733
|
-
try {
|
|
734
|
-
return await call();
|
|
735
|
-
} catch (error) {
|
|
736
|
-
if (!this.isClosedTransportError(error)) {
|
|
737
|
-
throw error;
|
|
738
|
-
}
|
|
739
|
-
api.logger.debug("[CodexMCP] Transport closed during tool call; reconnecting and retrying once");
|
|
740
|
-
try {
|
|
741
|
-
await this.disconnect();
|
|
742
|
-
} catch {
|
|
743
|
-
}
|
|
744
|
-
await this.connect();
|
|
745
|
-
return await call();
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
registerPermissionHandlers() {
|
|
749
|
-
this.client.setRequestHandler(
|
|
750
|
-
types_js.ElicitRequestSchema,
|
|
751
|
-
async (request) => {
|
|
752
|
-
const params = request.params;
|
|
753
|
-
api.logger.debug("[CodexMCP] Received elicitation request:", params);
|
|
754
|
-
try {
|
|
755
|
-
if (this.isSelectionRequest(params)) {
|
|
756
|
-
return await this.handleSelectionRequest(params);
|
|
757
|
-
}
|
|
758
|
-
if (!this.permissionHandler) {
|
|
759
|
-
api.logger.debug("[CodexMCP] No permission handler set, denying by default");
|
|
760
|
-
return {
|
|
761
|
-
action: "decline",
|
|
762
|
-
decision: "denied",
|
|
763
|
-
content: {
|
|
764
|
-
decision: "denied"
|
|
765
|
-
}
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
const normalized = resolveCodexPermissionRequest(
|
|
769
|
-
params,
|
|
770
|
-
this.takeExecApprovalContextForPermission(params)
|
|
771
|
-
);
|
|
772
|
-
const result = await this.permissionHandler.handleToolCall(
|
|
773
|
-
normalized.requestId,
|
|
774
|
-
normalized.toolName,
|
|
775
|
-
normalized.input
|
|
776
|
-
);
|
|
777
|
-
api.logger.debug("[CodexMCP] Permission result:", result);
|
|
778
|
-
return {
|
|
779
|
-
action: result.decision === "abort" ? "cancel" : result.decision === "denied" ? "decline" : "accept",
|
|
780
|
-
decision: result.decision,
|
|
781
|
-
content: {
|
|
782
|
-
decision: result.decision
|
|
783
|
-
}
|
|
784
|
-
};
|
|
785
|
-
} catch (error) {
|
|
786
|
-
if (error instanceof Error && error.message === "Session reset") {
|
|
787
|
-
api.logger.debug("[CodexMCP] Permission request cancelled during session reset");
|
|
788
|
-
return {
|
|
789
|
-
action: "cancel"
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
if (error instanceof Error && error.message === BaseReasoningProcessor.INTERACTION_SUPERSEDED_ERROR) {
|
|
793
|
-
api.logger.debug("[CodexMCP] Selection request cancelled because a new user message arrived");
|
|
794
|
-
return {
|
|
795
|
-
action: "cancel"
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
api.logger.debug("[CodexMCP] Error handling permission request:", error);
|
|
799
|
-
return {
|
|
800
|
-
action: "decline",
|
|
801
|
-
decision: "denied",
|
|
802
|
-
reason: error instanceof Error ? error.message : "Permission request failed"
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
);
|
|
807
|
-
api.logger.debug("[CodexMCP] Permission handlers registered");
|
|
808
|
-
}
|
|
809
|
-
isSelectionRequest(params) {
|
|
810
|
-
if (Array.isArray(params.codex_command) && params.codex_command.length > 0) {
|
|
811
|
-
return false;
|
|
812
|
-
}
|
|
813
|
-
if (Array.isArray(params.options) && params.options.length > 0) {
|
|
814
|
-
return true;
|
|
815
|
-
}
|
|
816
|
-
return Boolean(params.requestedSchema?.properties?.optionId);
|
|
817
|
-
}
|
|
818
|
-
async handleSelectionRequest(params) {
|
|
819
|
-
const optionMap = (params.options || []).filter((option) => Boolean(option.optionId));
|
|
820
|
-
const options = optionMap.map((option) => ({
|
|
821
|
-
optionId: option.optionId,
|
|
822
|
-
label: option.label || option.name || option.optionId,
|
|
823
|
-
description: option.description || option.name || option.optionId
|
|
824
|
-
}));
|
|
825
|
-
const defaultOptionId = params.requestedSchema?.properties?.optionId?.default;
|
|
826
|
-
if (options.length === 0) {
|
|
827
|
-
api.logger.debug("[CodexMCP] Selection request has no options, cancelling");
|
|
828
|
-
return {
|
|
829
|
-
action: "cancel"
|
|
830
|
-
};
|
|
831
|
-
}
|
|
832
|
-
if (!this.selectionHandler) {
|
|
833
|
-
api.logger.debug("[CodexMCP] No selection handler set, cancelling selection request");
|
|
834
|
-
return {
|
|
835
|
-
action: "cancel"
|
|
836
|
-
};
|
|
837
|
-
}
|
|
838
|
-
const requestId = params.codex_event_id || params.codex_elicitation || `selection-${Date.now()}`;
|
|
839
|
-
const response = await this.selectionHandler.requestSelection({
|
|
840
|
-
id: requestId,
|
|
841
|
-
message: params.message,
|
|
842
|
-
options,
|
|
843
|
-
defaultOptionId
|
|
844
|
-
});
|
|
845
|
-
return {
|
|
846
|
-
action: "accept",
|
|
847
|
-
optionId: response.optionId,
|
|
848
|
-
content: {
|
|
849
|
-
optionId: response.optionId
|
|
850
|
-
}
|
|
851
|
-
};
|
|
852
|
-
}
|
|
853
|
-
takeExecApprovalContextForPermission(params) {
|
|
854
|
-
const hasInlinePayload = Boolean(
|
|
855
|
-
params.codex_command || params.command || params.codex_cwd || params.cwd || params.proposed_execpolicy_amendment || params.parsed_cmd
|
|
856
|
-
);
|
|
857
|
-
const explicitId = params.codex_call_id || params.call_id || params.codex_mcp_tool_call_id || params.codex_event_id;
|
|
858
|
-
if (hasInlinePayload && explicitId) {
|
|
859
|
-
return null;
|
|
860
|
-
}
|
|
861
|
-
if (!this.pendingExecApprovalContexts.length) {
|
|
862
|
-
return null;
|
|
863
|
-
}
|
|
864
|
-
if (explicitId) {
|
|
865
|
-
const index = this.pendingExecApprovalContexts.findIndex((context) => context.call_id === explicitId);
|
|
866
|
-
if (index >= 0) {
|
|
867
|
-
return this.pendingExecApprovalContexts.splice(index, 1)[0] || null;
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
return this.pendingExecApprovalContexts.shift() || null;
|
|
871
|
-
}
|
|
872
|
-
async startSession(config, options) {
|
|
873
|
-
if (!this.connected) await this.connect();
|
|
874
|
-
api.logger.debug("[CodexMCP] Starting Codex session:", config);
|
|
875
|
-
const response = await this.callToolWithReconnectRetry(
|
|
876
|
-
() => this.client.callTool({
|
|
877
|
-
name: "codex",
|
|
878
|
-
arguments: config
|
|
879
|
-
}, void 0, {
|
|
880
|
-
signal: options?.signal,
|
|
881
|
-
timeout: DEFAULT_TIMEOUT
|
|
882
|
-
})
|
|
883
|
-
);
|
|
884
|
-
api.logger.debug("[CodexMCP] startSession response:", response);
|
|
885
|
-
this.extractIdentifiers(response);
|
|
886
|
-
return response;
|
|
887
|
-
}
|
|
888
|
-
async continueSession(prompt, options) {
|
|
889
|
-
if (!this.connected) await this.connect();
|
|
890
|
-
if (!this.sessionId) {
|
|
891
|
-
throw new Error("No active session. Call startSession first.");
|
|
892
|
-
}
|
|
893
|
-
if (!this.conversationId) {
|
|
894
|
-
this.conversationId = this.sessionId;
|
|
895
|
-
api.logger.debug("[CodexMCP] conversationId missing, defaulting to sessionId:", this.conversationId);
|
|
896
|
-
}
|
|
897
|
-
const args = { sessionId: this.sessionId, conversationId: this.conversationId, prompt };
|
|
898
|
-
api.logger.debug("[CodexMCP] Continuing Codex session:", args);
|
|
899
|
-
const response = await this.callToolWithReconnectRetry(
|
|
900
|
-
() => this.client.callTool({
|
|
901
|
-
name: "codex-reply",
|
|
902
|
-
arguments: args
|
|
903
|
-
}, void 0, {
|
|
904
|
-
signal: options?.signal,
|
|
905
|
-
timeout: DEFAULT_TIMEOUT
|
|
906
|
-
})
|
|
907
|
-
);
|
|
908
|
-
api.logger.debug("[CodexMCP] continueSession response:", response);
|
|
909
|
-
this.extractIdentifiers(response);
|
|
910
|
-
return response;
|
|
911
|
-
}
|
|
912
|
-
updateIdentifiersFromEvent(event) {
|
|
913
|
-
if (!event || typeof event !== "object") {
|
|
914
|
-
return;
|
|
915
|
-
}
|
|
916
|
-
const candidates = [event];
|
|
917
|
-
if (event.data && typeof event.data === "object") {
|
|
918
|
-
candidates.push(event.data);
|
|
919
|
-
}
|
|
920
|
-
for (const candidate of candidates) {
|
|
921
|
-
const sessionId = candidate.session_id ?? candidate.sessionId ?? candidate.thread_id ?? candidate.threadId;
|
|
922
|
-
if (sessionId) {
|
|
923
|
-
this.sessionId = sessionId;
|
|
924
|
-
api.logger.debug("[CodexMCP] Session ID extracted from event:", this.sessionId);
|
|
925
|
-
}
|
|
926
|
-
const conversationId = candidate.conversation_id ?? candidate.conversationId;
|
|
927
|
-
if (conversationId) {
|
|
928
|
-
this.conversationId = conversationId;
|
|
929
|
-
api.logger.debug("[CodexMCP] Conversation ID extracted from event:", this.conversationId);
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
extractIdentifiers(response) {
|
|
934
|
-
const identifiers = extractCodexIdentifiers(response);
|
|
935
|
-
if (identifiers.sessionId) {
|
|
936
|
-
this.sessionId = identifiers.sessionId;
|
|
937
|
-
api.logger.debug("[CodexMCP] Session ID extracted:", this.sessionId);
|
|
938
|
-
}
|
|
939
|
-
if (identifiers.conversationId) {
|
|
940
|
-
this.conversationId = identifiers.conversationId;
|
|
941
|
-
api.logger.debug("[CodexMCP] Conversation ID extracted:", this.conversationId);
|
|
942
|
-
}
|
|
943
|
-
const content = response?.content;
|
|
944
|
-
if (Array.isArray(content)) {
|
|
945
|
-
for (const item of content) {
|
|
946
|
-
if (!this.sessionId && item?.sessionId) {
|
|
947
|
-
this.sessionId = item.sessionId;
|
|
948
|
-
api.logger.debug("[CodexMCP] Session ID extracted from content:", this.sessionId);
|
|
949
|
-
}
|
|
950
|
-
if (!this.conversationId && item && typeof item === "object" && "conversationId" in item && item.conversationId) {
|
|
951
|
-
this.conversationId = item.conversationId;
|
|
952
|
-
api.logger.debug("[CodexMCP] Conversation ID extracted from content:", this.conversationId);
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
getSessionId() {
|
|
958
|
-
return this.sessionId;
|
|
959
|
-
}
|
|
960
|
-
hasActiveSession() {
|
|
961
|
-
return this.sessionId !== null;
|
|
962
|
-
}
|
|
963
|
-
clearSession() {
|
|
964
|
-
const previousSessionId = this.sessionId;
|
|
965
|
-
this.sessionId = null;
|
|
966
|
-
this.conversationId = null;
|
|
967
|
-
api.logger.debug("[CodexMCP] Session cleared, previous sessionId:", previousSessionId);
|
|
968
|
-
}
|
|
969
|
-
/**
|
|
970
|
-
* Store the current session ID without clearing it, useful for abort handling
|
|
971
|
-
*/
|
|
972
|
-
storeSessionForResume() {
|
|
973
|
-
api.logger.debug("[CodexMCP] Storing session for potential resume:", this.sessionId);
|
|
974
|
-
return this.sessionId;
|
|
975
|
-
}
|
|
976
|
-
/**
|
|
977
|
-
* Force close the Codex MCP transport and clear all session identifiers.
|
|
978
|
-
* Use this for permanent shutdown (e.g. kill/exit). Prefer `disconnect()` for
|
|
979
|
-
* transient connection resets where you may want to keep the session id.
|
|
980
|
-
*/
|
|
981
|
-
async forceCloseSession() {
|
|
982
|
-
api.logger.debug("[CodexMCP] Force closing session");
|
|
983
|
-
try {
|
|
984
|
-
await this.disconnect();
|
|
985
|
-
} finally {
|
|
986
|
-
this.clearSession();
|
|
987
|
-
}
|
|
988
|
-
api.logger.debug("[CodexMCP] Session force-closed");
|
|
989
|
-
}
|
|
990
|
-
async disconnect() {
|
|
991
|
-
if (!this.connected) return;
|
|
992
|
-
const pid = this.transport?.pid ?? null;
|
|
993
|
-
api.logger.debug(`[CodexMCP] Disconnecting; child pid=${pid ?? "none"}`);
|
|
994
|
-
try {
|
|
995
|
-
api.logger.debug("[CodexMCP] client.close begin");
|
|
996
|
-
await this.client.close();
|
|
997
|
-
api.logger.debug("[CodexMCP] client.close done");
|
|
998
|
-
} catch (e) {
|
|
999
|
-
api.logger.debug("[CodexMCP] Error closing client, attempting transport close directly", e);
|
|
1000
|
-
try {
|
|
1001
|
-
api.logger.debug("[CodexMCP] transport.close begin");
|
|
1002
|
-
await this.transport?.close?.();
|
|
1003
|
-
api.logger.debug("[CodexMCP] transport.close done");
|
|
1004
|
-
} catch {
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
if (pid) {
|
|
1008
|
-
try {
|
|
1009
|
-
process.kill(pid, 0);
|
|
1010
|
-
api.logger.debug("[CodexMCP] Child still alive, sending SIGKILL");
|
|
1011
|
-
try {
|
|
1012
|
-
process.kill(pid, "SIGKILL");
|
|
1013
|
-
} catch {
|
|
1014
|
-
}
|
|
1015
|
-
} catch {
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
this.transport = null;
|
|
1019
|
-
this.connected = false;
|
|
1020
|
-
this.client = this.createClient();
|
|
1021
|
-
api.logger.debug(`[CodexMCP] Disconnected; session ${this.sessionId ?? "none"} preserved`);
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
602
|
class CodexPermissionHandler extends BaseReasoningProcessor.BasePermissionHandler {
|
|
1026
603
|
constructor(session) {
|
|
1027
604
|
super(session);
|
|
@@ -1037,16 +614,7 @@ class CodexPermissionHandler extends BaseReasoningProcessor.BasePermissionHandle
|
|
|
1037
614
|
* @returns Promise resolving to permission result
|
|
1038
615
|
*/
|
|
1039
616
|
async handleToolCall(toolCallId, toolName, input) {
|
|
1040
|
-
return
|
|
1041
|
-
this.pendingRequests.set(toolCallId, {
|
|
1042
|
-
resolve,
|
|
1043
|
-
reject,
|
|
1044
|
-
toolName,
|
|
1045
|
-
input
|
|
1046
|
-
});
|
|
1047
|
-
this.addPendingRequestToState(toolCallId, toolName, input);
|
|
1048
|
-
api.logger.debug(`${this.getLogPrefix()} Permission request sent for tool: ${toolName} (${toolCallId})`);
|
|
1049
|
-
});
|
|
617
|
+
return this.registerPendingRequest(toolCallId, toolName, input);
|
|
1050
618
|
}
|
|
1051
619
|
}
|
|
1052
620
|
|
|
@@ -1063,11 +631,15 @@ class CodexSelectionHandler {
|
|
|
1063
631
|
}
|
|
1064
632
|
async requestSelection(request) {
|
|
1065
633
|
return new Promise((resolve, reject) => {
|
|
1066
|
-
|
|
634
|
+
const pending = {
|
|
1067
635
|
resolve,
|
|
1068
636
|
reject,
|
|
1069
637
|
request
|
|
1070
|
-
}
|
|
638
|
+
};
|
|
639
|
+
pending.timeoutHandle = setTimeout(() => {
|
|
640
|
+
this.handleSelectionTimeout(request.id, pending);
|
|
641
|
+
}, BaseReasoningProcessor.getPendingInteractionTimeoutMs());
|
|
642
|
+
this.pendingRequests.set(request.id, pending);
|
|
1071
643
|
this.session.updateAgentState((currentState) => ({
|
|
1072
644
|
...currentState,
|
|
1073
645
|
requests: {
|
|
@@ -1110,6 +682,7 @@ class CodexSelectionHandler {
|
|
|
1110
682
|
this.pendingRequests.clear();
|
|
1111
683
|
const completedAt = Date.now();
|
|
1112
684
|
for (const [, pending] of pendingSnapshot) {
|
|
685
|
+
this.clearPendingRequestTimeout(pending);
|
|
1113
686
|
pending.reject(new Error(reason));
|
|
1114
687
|
}
|
|
1115
688
|
this.session.updateAgentState((currentState) => {
|
|
@@ -1141,6 +714,7 @@ class CodexSelectionHandler {
|
|
|
1141
714
|
const pendingSnapshot = Array.from(this.pendingRequests.entries());
|
|
1142
715
|
this.pendingRequests.clear();
|
|
1143
716
|
for (const [, pending] of pendingSnapshot) {
|
|
717
|
+
this.clearPendingRequestTimeout(pending);
|
|
1144
718
|
pending.reject(new Error(reason));
|
|
1145
719
|
}
|
|
1146
720
|
this.session.updateAgentState((currentState) => {
|
|
@@ -1174,6 +748,7 @@ class CodexSelectionHandler {
|
|
|
1174
748
|
return;
|
|
1175
749
|
}
|
|
1176
750
|
this.pendingRequests.delete(response.id);
|
|
751
|
+
this.clearPendingRequestTimeout(pending);
|
|
1177
752
|
pending.resolve(response);
|
|
1178
753
|
this.session.updateAgentState((currentState) => {
|
|
1179
754
|
const request = currentState.requests?.[response.id];
|
|
@@ -1198,6 +773,54 @@ class CodexSelectionHandler {
|
|
|
1198
773
|
});
|
|
1199
774
|
});
|
|
1200
775
|
}
|
|
776
|
+
clearPendingRequestTimeout(pending) {
|
|
777
|
+
if (pending?.timeoutHandle) {
|
|
778
|
+
clearTimeout(pending.timeoutHandle);
|
|
779
|
+
pending.timeoutHandle = void 0;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
handleSelectionTimeout(requestId, pending) {
|
|
783
|
+
const active = this.pendingRequests.get(requestId);
|
|
784
|
+
if (!active || active !== pending) {
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
this.pendingRequests.delete(requestId);
|
|
788
|
+
this.clearPendingRequestTimeout(active);
|
|
789
|
+
active.reject(new Error(BaseReasoningProcessor.INTERACTION_TIMED_OUT_ERROR));
|
|
790
|
+
this.session.updateAgentState((currentState) => {
|
|
791
|
+
const request = currentState.requests?.[requestId] || {
|
|
792
|
+
tool: "AskUserQuestion",
|
|
793
|
+
arguments: {
|
|
794
|
+
requestKind: "selection",
|
|
795
|
+
questions: []
|
|
796
|
+
},
|
|
797
|
+
createdAt: Date.now(),
|
|
798
|
+
requestKind: "selection",
|
|
799
|
+
options: active.request.options,
|
|
800
|
+
defaultOptionId: active.request.defaultOptionId
|
|
801
|
+
};
|
|
802
|
+
const { [requestId]: _, ...remainingRequests } = currentState.requests || {};
|
|
803
|
+
return {
|
|
804
|
+
...currentState,
|
|
805
|
+
requests: remainingRequests,
|
|
806
|
+
completedRequests: {
|
|
807
|
+
...currentState.completedRequests,
|
|
808
|
+
[requestId]: {
|
|
809
|
+
...request,
|
|
810
|
+
completedAt: Date.now(),
|
|
811
|
+
status: "canceled",
|
|
812
|
+
reason: BaseReasoningProcessor.INTERACTION_TIMED_OUT_ERROR,
|
|
813
|
+
requestKind: "selection"
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
});
|
|
818
|
+
this.session.sendSessionEvent({
|
|
819
|
+
type: "message",
|
|
820
|
+
message: "Pending interaction timed out waiting for a response. Send a new message to continue."
|
|
821
|
+
});
|
|
822
|
+
api.logger.debug(`[Codex] Selection request timed out (${requestId})`);
|
|
823
|
+
}
|
|
1201
824
|
}
|
|
1202
825
|
|
|
1203
826
|
class ReasoningProcessor extends BaseReasoningProcessor.BaseReasoningProcessor {
|
|
@@ -1219,67 +842,62 @@ class ReasoningProcessor extends BaseReasoningProcessor.BaseReasoningProcessor {
|
|
|
1219
842
|
complete(fullText) {
|
|
1220
843
|
this.completeReasoning(fullText);
|
|
1221
844
|
}
|
|
845
|
+
completeCurrent() {
|
|
846
|
+
return this.completeReasoning();
|
|
847
|
+
}
|
|
1222
848
|
}
|
|
1223
849
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
*/
|
|
1233
|
-
processDiff(unifiedDiff) {
|
|
1234
|
-
if (this.previousDiff !== unifiedDiff) {
|
|
1235
|
-
api.logger.debug("[DiffProcessor] Unified diff changed, sending CodexDiff tool call");
|
|
1236
|
-
const callId = node_crypto.randomUUID();
|
|
1237
|
-
const toolCall = {
|
|
1238
|
-
type: "tool-call",
|
|
1239
|
-
name: "CodexDiff",
|
|
1240
|
-
callId,
|
|
1241
|
-
input: {
|
|
1242
|
-
unified_diff: unifiedDiff
|
|
1243
|
-
},
|
|
1244
|
-
id: node_crypto.randomUUID()
|
|
1245
|
-
};
|
|
1246
|
-
this.onMessage?.(toolCall);
|
|
1247
|
-
const toolResult = {
|
|
1248
|
-
type: "tool-call-result",
|
|
1249
|
-
callId,
|
|
1250
|
-
output: {
|
|
1251
|
-
status: "completed"
|
|
1252
|
-
},
|
|
1253
|
-
id: node_crypto.randomUUID()
|
|
1254
|
-
};
|
|
1255
|
-
this.onMessage?.(toolResult);
|
|
1256
|
-
}
|
|
1257
|
-
this.previousDiff = unifiedDiff;
|
|
1258
|
-
api.logger.debug("[DiffProcessor] Updated stored diff");
|
|
1259
|
-
}
|
|
1260
|
-
/**
|
|
1261
|
-
* Reset the processor state (called on task_complete or turn_aborted)
|
|
1262
|
-
*/
|
|
1263
|
-
reset() {
|
|
1264
|
-
api.logger.debug("[DiffProcessor] Resetting diff state");
|
|
1265
|
-
this.previousDiff = null;
|
|
1266
|
-
}
|
|
1267
|
-
/**
|
|
1268
|
-
* Set the message callback for sending messages directly
|
|
1269
|
-
*/
|
|
1270
|
-
setMessageCallback(callback) {
|
|
1271
|
-
this.onMessage = callback;
|
|
850
|
+
function createAbortError() {
|
|
851
|
+
const error = new Error("Operation aborted");
|
|
852
|
+
error.name = "AbortError";
|
|
853
|
+
return error;
|
|
854
|
+
}
|
|
855
|
+
async function waitForResponseCompleteWithAbort(backend, signal, timeoutMs = 12e4) {
|
|
856
|
+
if (!backend.waitForResponseComplete) {
|
|
857
|
+
return;
|
|
1272
858
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
*/
|
|
1276
|
-
getCurrentDiff() {
|
|
1277
|
-
return this.previousDiff;
|
|
859
|
+
if (signal.aborted) {
|
|
860
|
+
throw createAbortError();
|
|
1278
861
|
}
|
|
862
|
+
await new Promise((resolve, reject) => {
|
|
863
|
+
const onAbort = () => reject(createAbortError());
|
|
864
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
865
|
+
backend.waitForResponseComplete(timeoutMs).then(resolve).catch(reject).finally(() => {
|
|
866
|
+
signal.removeEventListener("abort", onAbort);
|
|
867
|
+
});
|
|
868
|
+
});
|
|
1279
869
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
870
|
+
function normalizeCodexBackendError(error) {
|
|
871
|
+
const record = typeof error === "object" && error !== null ? error : null;
|
|
872
|
+
const text = index.formatDisplayMessage(error).trim();
|
|
873
|
+
const stderrText = record ? index.formatDisplayMessage(record.stderr).trim() : "";
|
|
874
|
+
const detailText = record ? index.formatDisplayMessage(record.detail).trim() : "";
|
|
875
|
+
const searchableText = [text, stderrText, detailText].filter(Boolean).join("\n");
|
|
876
|
+
const prefix = typeof error === "object" && error !== null ? [
|
|
877
|
+
record?.code !== void 0 && record?.code !== null ? `[code=${String(record.code)}]` : "",
|
|
878
|
+
record?.status !== void 0 && record?.status !== null ? `[status=${String(record.status)}]` : ""
|
|
879
|
+
].filter(Boolean).join(" ") : "";
|
|
880
|
+
if (searchableText.includes("Failed to locate @zed-industries/codex-acp-")) {
|
|
881
|
+
const hint = "Codex ACP could not start because the @zed-industries/codex-acp platform binary is missing. Reinstall codex-acp for this machine, or set HAPPY_CODEX_ACP_BIN to a working codex-acp executable.";
|
|
882
|
+
return prefix ? `${prefix} ${hint}` : hint;
|
|
883
|
+
}
|
|
884
|
+
if (searchableText.includes("method not found: initialize") || searchableText.includes("method not found: session/new")) {
|
|
885
|
+
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.";
|
|
886
|
+
return prefix ? `${prefix} ${hint}` : hint;
|
|
887
|
+
}
|
|
888
|
+
if (error instanceof Error && text) {
|
|
889
|
+
return text;
|
|
890
|
+
}
|
|
891
|
+
if (typeof error === "object" && error !== null && !Array.isArray(error) && Object.keys(error).length === 0) {
|
|
892
|
+
return "Codex backend exited unexpectedly";
|
|
893
|
+
}
|
|
894
|
+
if (typeof error === "object" && error !== null) {
|
|
895
|
+
const message = text;
|
|
896
|
+
if (message) {
|
|
897
|
+
return prefix ? `${prefix} ${message}` : message;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return text || "Codex backend exited unexpectedly";
|
|
1283
901
|
}
|
|
1284
902
|
function shouldEmitCodexReadyAfterWait(opts) {
|
|
1285
903
|
if (opts.readyAlreadySent) {
|
|
@@ -1292,6 +910,68 @@ function registerCodexRemoteRpcHandlers(clientSession, handlers) {
|
|
|
1292
910
|
clientSession.rpcHandlerManager.registerHandler("switch", handlers.handleSwitchToLocal);
|
|
1293
911
|
registerKillSessionHandler.registerKillSessionHandler(clientSession.rpcHandlerManager, handlers.handleKillSession);
|
|
1294
912
|
}
|
|
913
|
+
function handleIncomingCodexMessageDuringRemoteTurn(opts) {
|
|
914
|
+
const reason = opts.reason ?? BaseReasoningProcessor.INTERACTION_SUPERSEDED_ERROR;
|
|
915
|
+
const supersededPermissions = opts.supersedePermissions(reason);
|
|
916
|
+
const supersededSelections = opts.supersedeSelections(reason);
|
|
917
|
+
const interruptedTurn = opts.turnInFlight;
|
|
918
|
+
const total = supersededPermissions + supersededSelections;
|
|
919
|
+
if (interruptedTurn) {
|
|
920
|
+
opts.abortActiveTurn();
|
|
921
|
+
}
|
|
922
|
+
if (total > 0 || interruptedTurn) {
|
|
923
|
+
const message = interruptedTurn ? "Current Codex task was canceled because a new user message arrived. Processing the latest message instead." : "Previous pending interaction was canceled because a new user message arrived. Processing the latest message instead.";
|
|
924
|
+
opts.onInterrupted(message);
|
|
925
|
+
}
|
|
926
|
+
return total > 0 || interruptedTurn;
|
|
927
|
+
}
|
|
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
|
+
}
|
|
1295
975
|
async function codexRemoteLauncher(session) {
|
|
1296
976
|
const messageBuffer = new registerKillSessionHandler.MessageBuffer();
|
|
1297
977
|
const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
|
|
@@ -1299,46 +979,26 @@ async function codexRemoteLauncher(session) {
|
|
|
1299
979
|
let exitReason = null;
|
|
1300
980
|
let shouldExit = false;
|
|
1301
981
|
let abortController = new AbortController();
|
|
1302
|
-
let
|
|
1303
|
-
|
|
1304
|
-
let
|
|
982
|
+
let turnInFlight = false;
|
|
983
|
+
let backend = null;
|
|
984
|
+
let acpSessionId = null;
|
|
985
|
+
let wasSessionCreated = false;
|
|
986
|
+
let currentModeHash = null;
|
|
987
|
+
let pending = null;
|
|
988
|
+
let readyAlreadySent = false;
|
|
989
|
+
let accumulatedResponse = "";
|
|
990
|
+
let isResponseInProgress = false;
|
|
991
|
+
let taskStartedSent = false;
|
|
1305
992
|
const permissionHandler = new CodexPermissionHandler(session.client);
|
|
1306
993
|
const selectionHandler = new CodexSelectionHandler(session.client);
|
|
1307
994
|
const reasoningProcessor = new ReasoningProcessor((message) => {
|
|
1308
995
|
session.runtimeSession.sendCodexMessage(message);
|
|
1309
996
|
});
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
api.logger.debug("[Codex] Switching to local from RPC");
|
|
1315
|
-
exitReason = "switch";
|
|
1316
|
-
shouldExit = true;
|
|
1317
|
-
await handleAbort();
|
|
1318
|
-
};
|
|
1319
|
-
client.setPermissionHandler(permissionHandler);
|
|
1320
|
-
client.setSelectionHandler(selectionHandler);
|
|
1321
|
-
session.setPendingInteractionSuperseder((reason = BaseReasoningProcessor.INTERACTION_SUPERSEDED_ERROR) => {
|
|
1322
|
-
const supersededPermissions = permissionHandler.supersedePendingRequests(reason);
|
|
1323
|
-
const supersededSelections = selectionHandler.supersedePendingRequests(reason);
|
|
1324
|
-
const total = supersededPermissions + supersededSelections;
|
|
1325
|
-
if (total > 0) {
|
|
1326
|
-
const message = "Previous pending interaction was canceled because a new user message arrived. Processing the latest message instead.";
|
|
1327
|
-
messageBuffer.addMessage(message, "status");
|
|
1328
|
-
session.runtimeSession.sendSessionEvent({ type: "message", message });
|
|
1329
|
-
}
|
|
1330
|
-
return total > 0;
|
|
1331
|
-
});
|
|
1332
|
-
const handleClientSwap = (clientSession) => {
|
|
1333
|
-
permissionHandler.updateSession(clientSession);
|
|
1334
|
-
selectionHandler.updateSession(clientSession);
|
|
1335
|
-
registerCodexRemoteRpcHandlers(clientSession, {
|
|
1336
|
-
handleAbort,
|
|
1337
|
-
handleSwitchToLocal,
|
|
1338
|
-
handleKillSession
|
|
1339
|
-
});
|
|
997
|
+
const rotateAbortController = () => {
|
|
998
|
+
const activeController = abortController;
|
|
999
|
+
abortController = new AbortController();
|
|
1000
|
+
return activeController;
|
|
1340
1001
|
};
|
|
1341
|
-
session.addClientSwapCallback(handleClientSwap);
|
|
1342
1002
|
const sendReady = () => {
|
|
1343
1003
|
session.runtimeSession.sendSessionEvent({ type: "ready" });
|
|
1344
1004
|
try {
|
|
@@ -1351,18 +1011,282 @@ async function codexRemoteLauncher(session) {
|
|
|
1351
1011
|
api.logger.debug("[Codex] Failed to send ready push", pushError);
|
|
1352
1012
|
}
|
|
1353
1013
|
};
|
|
1014
|
+
const resetTurnState = () => {
|
|
1015
|
+
reasoningProcessor.abort();
|
|
1016
|
+
accumulatedResponse = "";
|
|
1017
|
+
isResponseInProgress = false;
|
|
1018
|
+
taskStartedSent = false;
|
|
1019
|
+
session.onThinkingChange(false);
|
|
1020
|
+
};
|
|
1021
|
+
const abortActiveTurn = () => {
|
|
1022
|
+
const activeController = rotateAbortController();
|
|
1023
|
+
activeController.abort();
|
|
1024
|
+
reasoningProcessor.abort();
|
|
1025
|
+
session.onThinkingChange(false);
|
|
1026
|
+
if (backend && acpSessionId) {
|
|
1027
|
+
void backend.cancel(acpSessionId).catch((error) => {
|
|
1028
|
+
api.logger.debug("[Codex] Error cancelling ACP session:", error);
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
const disposeBackend = async () => {
|
|
1033
|
+
if (!backend) {
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
const currentBackend = backend;
|
|
1037
|
+
backend = null;
|
|
1038
|
+
acpSessionId = null;
|
|
1039
|
+
wasSessionCreated = false;
|
|
1040
|
+
try {
|
|
1041
|
+
await currentBackend.dispose();
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
api.logger.debug("[Codex] Error disposing ACP backend:", error);
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
const emitFinalAssistantMessage = () => {
|
|
1047
|
+
if (!accumulatedResponse.trim()) {
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
session.runtimeSession.sendCodexMessage({
|
|
1051
|
+
type: "message",
|
|
1052
|
+
message: accumulatedResponse,
|
|
1053
|
+
id: node_crypto.randomUUID()
|
|
1054
|
+
});
|
|
1055
|
+
accumulatedResponse = "";
|
|
1056
|
+
isResponseInProgress = false;
|
|
1057
|
+
};
|
|
1058
|
+
const setupBackendMessageHandler = (currentBackend) => {
|
|
1059
|
+
currentBackend.onMessage((msg) => {
|
|
1060
|
+
switch (msg.type) {
|
|
1061
|
+
case "model-output": {
|
|
1062
|
+
const text = msg.textDelta ?? msg.fullText ?? "";
|
|
1063
|
+
if (!text) {
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
if (!isResponseInProgress) {
|
|
1067
|
+
messageBuffer.removeLastMessage("system");
|
|
1068
|
+
messageBuffer.addMessage(text, "assistant");
|
|
1069
|
+
isResponseInProgress = true;
|
|
1070
|
+
} else {
|
|
1071
|
+
messageBuffer.updateLastMessage(text, "assistant");
|
|
1072
|
+
}
|
|
1073
|
+
accumulatedResponse += text;
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
case "status": {
|
|
1077
|
+
if (msg.status === "running") {
|
|
1078
|
+
session.onThinkingChange(true);
|
|
1079
|
+
if (!taskStartedSent) {
|
|
1080
|
+
session.runtimeSession.sendCodexMessage({
|
|
1081
|
+
type: "task_started",
|
|
1082
|
+
id: node_crypto.randomUUID()
|
|
1083
|
+
});
|
|
1084
|
+
taskStartedSent = true;
|
|
1085
|
+
}
|
|
1086
|
+
if (!isResponseInProgress) {
|
|
1087
|
+
messageBuffer.addMessage("Thinking...", "system");
|
|
1088
|
+
}
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
if (msg.status === "idle" || msg.status === "stopped") {
|
|
1092
|
+
reasoningProcessor.completeCurrent();
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (msg.status === "error") {
|
|
1096
|
+
const errorMessage = normalizeCodexBackendError(msg.detail);
|
|
1097
|
+
messageBuffer.addMessage(`Error: ${errorMessage}`, "status");
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
case "tool-call": {
|
|
1103
|
+
const toolArgs = msg.args ? index.truncateDisplayMessage(msg.args, 100) : "";
|
|
1104
|
+
messageBuffer.addMessage(
|
|
1105
|
+
`Executing: ${msg.toolName}${toolArgs ? ` ${toolArgs}` : ""}`,
|
|
1106
|
+
"tool"
|
|
1107
|
+
);
|
|
1108
|
+
session.runtimeSession.sendCodexMessage({
|
|
1109
|
+
type: "tool-call",
|
|
1110
|
+
name: msg.toolName,
|
|
1111
|
+
callId: msg.callId,
|
|
1112
|
+
input: msg.args,
|
|
1113
|
+
id: node_crypto.randomUUID()
|
|
1114
|
+
});
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
case "tool-result": {
|
|
1118
|
+
const isError = msg.result && typeof msg.result === "object" && "error" in msg.result;
|
|
1119
|
+
const resultText = index.truncateDisplayMessage(msg.result, 200) || (isError ? "Unknown error" : "");
|
|
1120
|
+
messageBuffer.addMessage(
|
|
1121
|
+
`${isError ? "Error:" : "Result:"} ${resultText}`.trim(),
|
|
1122
|
+
"result"
|
|
1123
|
+
);
|
|
1124
|
+
session.runtimeSession.sendCodexMessage({
|
|
1125
|
+
type: "tool-call-result",
|
|
1126
|
+
callId: msg.callId,
|
|
1127
|
+
output: msg.result,
|
|
1128
|
+
id: node_crypto.randomUUID()
|
|
1129
|
+
});
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
case "fs-edit": {
|
|
1133
|
+
messageBuffer.addMessage(`File edit: ${msg.description}`, "tool");
|
|
1134
|
+
session.runtimeSession.sendCodexMessage({
|
|
1135
|
+
type: "file-edit",
|
|
1136
|
+
description: msg.description,
|
|
1137
|
+
filePath: msg.path || "unknown",
|
|
1138
|
+
diff: msg.diff,
|
|
1139
|
+
id: node_crypto.randomUUID()
|
|
1140
|
+
});
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
case "terminal-output": {
|
|
1144
|
+
const terminalOutput = index.formatDisplayMessage(msg.data);
|
|
1145
|
+
messageBuffer.addMessage(terminalOutput, "result");
|
|
1146
|
+
session.runtimeSession.sendCodexMessage({
|
|
1147
|
+
type: "terminal-output",
|
|
1148
|
+
data: terminalOutput,
|
|
1149
|
+
callId: node_crypto.randomUUID()
|
|
1150
|
+
});
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
case "permission-request": {
|
|
1154
|
+
const payload = msg.payload || {};
|
|
1155
|
+
session.runtimeSession.sendCodexMessage({
|
|
1156
|
+
type: "permission-request",
|
|
1157
|
+
permissionId: msg.id,
|
|
1158
|
+
toolName: typeof payload.toolName === "string" ? payload.toolName : msg.reason,
|
|
1159
|
+
description: msg.reason,
|
|
1160
|
+
options: payload
|
|
1161
|
+
});
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
case "exec-approval-request": {
|
|
1165
|
+
const { call_id, type, ...inputs } = msg;
|
|
1166
|
+
messageBuffer.addMessage(`Exec approval requested: ${call_id}`, "tool");
|
|
1167
|
+
session.runtimeSession.sendCodexMessage({
|
|
1168
|
+
type: "tool-call",
|
|
1169
|
+
name: "CodexBash",
|
|
1170
|
+
callId: call_id,
|
|
1171
|
+
input: inputs,
|
|
1172
|
+
id: node_crypto.randomUUID()
|
|
1173
|
+
});
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
case "patch-apply-begin": {
|
|
1177
|
+
const changeCount = Object.keys(msg.changes || {}).length;
|
|
1178
|
+
const filesMsg = changeCount === 1 ? "1 file" : `${changeCount} files`;
|
|
1179
|
+
messageBuffer.addMessage(`Modifying ${filesMsg}...`, "tool");
|
|
1180
|
+
session.runtimeSession.sendCodexMessage({
|
|
1181
|
+
type: "tool-call",
|
|
1182
|
+
name: "CodexPatch",
|
|
1183
|
+
callId: msg.call_id,
|
|
1184
|
+
input: {
|
|
1185
|
+
auto_approved: msg.auto_approved,
|
|
1186
|
+
changes: msg.changes
|
|
1187
|
+
},
|
|
1188
|
+
id: node_crypto.randomUUID()
|
|
1189
|
+
});
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
case "patch-apply-end": {
|
|
1193
|
+
if (msg.success) {
|
|
1194
|
+
messageBuffer.addMessage(index.truncateDisplayMessage(msg.stdout || "Files modified successfully", 200), "result");
|
|
1195
|
+
} else {
|
|
1196
|
+
messageBuffer.addMessage(`Error: ${index.truncateDisplayMessage(msg.stderr || "Failed to modify files", 200)}`, "result");
|
|
1197
|
+
}
|
|
1198
|
+
session.runtimeSession.sendCodexMessage({
|
|
1199
|
+
type: "tool-call-result",
|
|
1200
|
+
callId: msg.call_id,
|
|
1201
|
+
output: {
|
|
1202
|
+
stdout: msg.stdout,
|
|
1203
|
+
stderr: msg.stderr,
|
|
1204
|
+
success: msg.success
|
|
1205
|
+
},
|
|
1206
|
+
id: node_crypto.randomUUID()
|
|
1207
|
+
});
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
case "token-count": {
|
|
1211
|
+
session.runtimeSession.sendCodexMessage({
|
|
1212
|
+
...msg,
|
|
1213
|
+
id: node_crypto.randomUUID()
|
|
1214
|
+
});
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
case "event": {
|
|
1218
|
+
if (msg.name === "thinking") {
|
|
1219
|
+
const thinkingPayload = msg.payload;
|
|
1220
|
+
const thinkingText = typeof thinkingPayload?.text === "string" ? thinkingPayload.text : "";
|
|
1221
|
+
if (thinkingText) {
|
|
1222
|
+
reasoningProcessor.processDelta(thinkingText);
|
|
1223
|
+
if (!thinkingText.startsWith("**")) {
|
|
1224
|
+
messageBuffer.updateLastMessage(`[Thinking] ${thinkingText.substring(0, 100)}...`, "system");
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
default:
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
};
|
|
1235
|
+
const createBackend = (mode) => {
|
|
1236
|
+
const executionMode = resolveCodexAcpExecutionMode(mode);
|
|
1237
|
+
const validation = index.validateCodexAcpSpawn({
|
|
1238
|
+
command: process.env.HAPPY_CODEX_ACP_COMMAND,
|
|
1239
|
+
model: executionMode.model,
|
|
1240
|
+
sandbox: executionMode.sandbox,
|
|
1241
|
+
approvalPolicy: executionMode.approvalPolicy,
|
|
1242
|
+
baseArgs: session.codexArgs
|
|
1243
|
+
});
|
|
1244
|
+
if (!validation.ok) {
|
|
1245
|
+
throw new Error(validation.errorMessage);
|
|
1246
|
+
}
|
|
1247
|
+
const result = index.createCodexBackend({
|
|
1248
|
+
cwd: session.path,
|
|
1249
|
+
baseArgs: session.codexArgs,
|
|
1250
|
+
model: executionMode.model,
|
|
1251
|
+
sandbox: executionMode.sandbox,
|
|
1252
|
+
approvalPolicy: executionMode.approvalPolicy,
|
|
1253
|
+
mcpServers: {},
|
|
1254
|
+
permissionHandler,
|
|
1255
|
+
selectionHandler: {
|
|
1256
|
+
handleSelection: (request) => selectionHandler.requestSelection(request)
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
backend = result.backend;
|
|
1260
|
+
setupBackendMessageHandler(result.backend);
|
|
1261
|
+
return result.backend;
|
|
1262
|
+
};
|
|
1263
|
+
const handleSwitchToLocal = async () => {
|
|
1264
|
+
api.logger.debug("[Codex] Switching to local from RPC");
|
|
1265
|
+
session.pinLocalMode();
|
|
1266
|
+
exitReason = "switch";
|
|
1267
|
+
shouldExit = true;
|
|
1268
|
+
await handleAbort();
|
|
1269
|
+
};
|
|
1270
|
+
session.setPendingInteractionSuperseder((reason = BaseReasoningProcessor.INTERACTION_SUPERSEDED_ERROR) => {
|
|
1271
|
+
return handleIncomingCodexMessageDuringRemoteTurn({
|
|
1272
|
+
reason,
|
|
1273
|
+
supersedePermissions: (value) => permissionHandler.supersedePendingRequests(value),
|
|
1274
|
+
supersedeSelections: (value) => selectionHandler.supersedePendingRequests(value),
|
|
1275
|
+
turnInFlight,
|
|
1276
|
+
abortActiveTurn,
|
|
1277
|
+
onInterrupted: (message) => {
|
|
1278
|
+
api.logger.debug("[Codex] Incoming user message interrupted ACP remote turn");
|
|
1279
|
+
messageBuffer.addMessage(message, "status");
|
|
1280
|
+
session.runtimeSession.sendSessionEvent({ type: "message", message });
|
|
1281
|
+
}
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
1354
1284
|
async function handleAbort() {
|
|
1355
1285
|
api.logger.debug("[Codex] Abort requested - stopping current task");
|
|
1356
1286
|
try {
|
|
1357
|
-
|
|
1358
|
-
storedSessionIdForResume = client.storeSessionForResume();
|
|
1359
|
-
}
|
|
1360
|
-
abortController.abort();
|
|
1361
|
-
reasoningProcessor.abort();
|
|
1287
|
+
abortActiveTurn();
|
|
1362
1288
|
} catch (error) {
|
|
1363
1289
|
api.logger.debug("[Codex] Error during abort:", error);
|
|
1364
|
-
} finally {
|
|
1365
|
-
abortController = new AbortController();
|
|
1366
1290
|
}
|
|
1367
1291
|
}
|
|
1368
1292
|
async function handleKillSession() {
|
|
@@ -1379,7 +1303,7 @@ async function codexRemoteLauncher(session) {
|
|
|
1379
1303
|
session.runtimeSession.sendSessionDeath();
|
|
1380
1304
|
await session.runtimeSession.flush();
|
|
1381
1305
|
await session.runtimeSession.close();
|
|
1382
|
-
await
|
|
1306
|
+
await disposeBackend();
|
|
1383
1307
|
index.stopCaffeinate();
|
|
1384
1308
|
process.exit(0);
|
|
1385
1309
|
} catch (error) {
|
|
@@ -1387,6 +1311,16 @@ async function codexRemoteLauncher(session) {
|
|
|
1387
1311
|
process.exit(1);
|
|
1388
1312
|
}
|
|
1389
1313
|
}
|
|
1314
|
+
const handleClientSwap = (clientSession) => {
|
|
1315
|
+
permissionHandler.updateSession(clientSession);
|
|
1316
|
+
selectionHandler.updateSession(clientSession);
|
|
1317
|
+
registerCodexRemoteRpcHandlers(clientSession, {
|
|
1318
|
+
handleAbort,
|
|
1319
|
+
handleSwitchToLocal,
|
|
1320
|
+
handleKillSession
|
|
1321
|
+
});
|
|
1322
|
+
};
|
|
1323
|
+
session.addClientSwapCallback(handleClientSwap);
|
|
1390
1324
|
if (hasTTY) {
|
|
1391
1325
|
console.clear();
|
|
1392
1326
|
inkInstance = ink.render(React.createElement(CodexDisplay, {
|
|
@@ -1401,6 +1335,7 @@ async function codexRemoteLauncher(session) {
|
|
|
1401
1335
|
},
|
|
1402
1336
|
onSwitchToLocal: async () => {
|
|
1403
1337
|
api.logger.debug("[Codex] Switching to local from local keyboard");
|
|
1338
|
+
session.pinLocalMode();
|
|
1404
1339
|
exitReason = "switch";
|
|
1405
1340
|
shouldExit = true;
|
|
1406
1341
|
await handleAbort();
|
|
@@ -1422,116 +1357,7 @@ async function codexRemoteLauncher(session) {
|
|
|
1422
1357
|
handleSwitchToLocal,
|
|
1423
1358
|
handleKillSession
|
|
1424
1359
|
});
|
|
1425
|
-
client.setHandler((msg) => {
|
|
1426
|
-
api.logger.debug(`[Codex] Message: ${JSON.stringify(msg)}`);
|
|
1427
|
-
if (msg.type === "agent_message") {
|
|
1428
|
-
messageBuffer.addMessage(msg.message, "assistant");
|
|
1429
|
-
} else if (msg.type === "agent_reasoning") {
|
|
1430
|
-
messageBuffer.addMessage(`[Thinking] ${msg.text.substring(0, 100)}...`, "system");
|
|
1431
|
-
} else if (msg.type === "exec_command_begin") {
|
|
1432
|
-
messageBuffer.addMessage(`Executing: ${msg.command}`, "tool");
|
|
1433
|
-
} else if (msg.type === "exec_command_end") {
|
|
1434
|
-
const output = msg.output || msg.error || "Command completed";
|
|
1435
|
-
const truncatedOutput = output.substring(0, 200);
|
|
1436
|
-
messageBuffer.addMessage(`Result: ${truncatedOutput}${output.length > 200 ? "..." : ""}`, "result");
|
|
1437
|
-
} else if (msg.type === "task_started") {
|
|
1438
|
-
messageBuffer.addMessage("Starting task...", "status");
|
|
1439
|
-
session.onThinkingChange(true);
|
|
1440
|
-
} else if (msg.type === "task_complete") {
|
|
1441
|
-
messageBuffer.addMessage("Task completed", "status");
|
|
1442
|
-
session.onThinkingChange(false);
|
|
1443
|
-
diffProcessor.reset();
|
|
1444
|
-
if (shouldEmitCodexReadyOnTurnEnd({ shouldExit, exitReason })) {
|
|
1445
|
-
sendReady();
|
|
1446
|
-
readyAlreadySent = true;
|
|
1447
|
-
}
|
|
1448
|
-
} else if (msg.type === "turn_aborted") {
|
|
1449
|
-
messageBuffer.addMessage("Turn aborted", "status");
|
|
1450
|
-
session.onThinkingChange(false);
|
|
1451
|
-
diffProcessor.reset();
|
|
1452
|
-
if (shouldEmitCodexReadyOnTurnEnd({ shouldExit, exitReason })) {
|
|
1453
|
-
sendReady();
|
|
1454
|
-
readyAlreadySent = true;
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
if (msg.type === "agent_reasoning_section_break") {
|
|
1458
|
-
reasoningProcessor.handleSectionBreak();
|
|
1459
|
-
}
|
|
1460
|
-
if (msg.type === "agent_reasoning_delta") {
|
|
1461
|
-
reasoningProcessor.processDelta(msg.delta);
|
|
1462
|
-
}
|
|
1463
|
-
if (msg.type === "agent_reasoning") {
|
|
1464
|
-
reasoningProcessor.complete(msg.text);
|
|
1465
|
-
}
|
|
1466
|
-
if (msg.type === "agent_message") {
|
|
1467
|
-
session.runtimeSession.sendCodexMessage({
|
|
1468
|
-
type: "message",
|
|
1469
|
-
message: msg.message,
|
|
1470
|
-
id: node_crypto.randomUUID()
|
|
1471
|
-
});
|
|
1472
|
-
}
|
|
1473
|
-
if (msg.type === "exec_command_begin" || msg.type === "exec_approval_request") {
|
|
1474
|
-
const { call_id, type, ...inputs } = msg;
|
|
1475
|
-
session.runtimeSession.sendCodexMessage({
|
|
1476
|
-
type: "tool-call",
|
|
1477
|
-
name: "CodexBash",
|
|
1478
|
-
callId: call_id,
|
|
1479
|
-
input: inputs,
|
|
1480
|
-
id: node_crypto.randomUUID()
|
|
1481
|
-
});
|
|
1482
|
-
}
|
|
1483
|
-
if (msg.type === "exec_command_end") {
|
|
1484
|
-
const { call_id, type, ...output } = msg;
|
|
1485
|
-
session.runtimeSession.sendCodexMessage({
|
|
1486
|
-
type: "tool-call-result",
|
|
1487
|
-
callId: call_id,
|
|
1488
|
-
output,
|
|
1489
|
-
id: node_crypto.randomUUID()
|
|
1490
|
-
});
|
|
1491
|
-
}
|
|
1492
|
-
if (msg.type === "token_count") {
|
|
1493
|
-
session.runtimeSession.sendCodexMessage({
|
|
1494
|
-
...msg,
|
|
1495
|
-
id: node_crypto.randomUUID()
|
|
1496
|
-
});
|
|
1497
|
-
}
|
|
1498
|
-
if (msg.type === "patch_apply_begin") {
|
|
1499
|
-
const { call_id, auto_approved, changes } = msg;
|
|
1500
|
-
const changeCount = Object.keys(changes).length;
|
|
1501
|
-
const filesMsg = changeCount === 1 ? "1 file" : `${changeCount} files`;
|
|
1502
|
-
messageBuffer.addMessage(`Modifying ${filesMsg}...`, "tool");
|
|
1503
|
-
session.runtimeSession.sendCodexMessage({
|
|
1504
|
-
type: "tool-call",
|
|
1505
|
-
name: "CodexPatch",
|
|
1506
|
-
callId: call_id,
|
|
1507
|
-
input: { auto_approved, changes },
|
|
1508
|
-
id: node_crypto.randomUUID()
|
|
1509
|
-
});
|
|
1510
|
-
}
|
|
1511
|
-
if (msg.type === "patch_apply_end") {
|
|
1512
|
-
const { call_id, stdout, stderr, success } = msg;
|
|
1513
|
-
if (success) {
|
|
1514
|
-
messageBuffer.addMessage((stdout || "Files modified successfully").substring(0, 200), "result");
|
|
1515
|
-
} else {
|
|
1516
|
-
messageBuffer.addMessage(`Error: ${(stderr || "Failed to modify files").substring(0, 200)}`, "result");
|
|
1517
|
-
}
|
|
1518
|
-
session.runtimeSession.sendCodexMessage({
|
|
1519
|
-
type: "tool-call-result",
|
|
1520
|
-
callId: call_id,
|
|
1521
|
-
output: { stdout, stderr, success },
|
|
1522
|
-
id: node_crypto.randomUUID()
|
|
1523
|
-
});
|
|
1524
|
-
}
|
|
1525
|
-
if (msg.type === "turn_diff" && msg.unified_diff) {
|
|
1526
|
-
diffProcessor.processDiff(msg.unified_diff);
|
|
1527
|
-
}
|
|
1528
|
-
});
|
|
1529
|
-
let readyAlreadySent = false;
|
|
1530
1360
|
try {
|
|
1531
|
-
await client.connect();
|
|
1532
|
-
let wasCreated = false;
|
|
1533
|
-
let currentModeHash = null;
|
|
1534
|
-
let pending = null;
|
|
1535
1361
|
while (!shouldExit) {
|
|
1536
1362
|
let message = pending;
|
|
1537
1363
|
pending = null;
|
|
@@ -1549,122 +1375,71 @@ async function codexRemoteLauncher(session) {
|
|
|
1549
1375
|
if (!message) {
|
|
1550
1376
|
break;
|
|
1551
1377
|
}
|
|
1552
|
-
if (
|
|
1378
|
+
if (wasSessionCreated && currentModeHash && message.hash !== currentModeHash) {
|
|
1553
1379
|
messageBuffer.addMessage("\u2550".repeat(40), "status");
|
|
1554
1380
|
messageBuffer.addMessage("Starting new Codex session (mode changed)...", "status");
|
|
1555
|
-
|
|
1556
|
-
pendingModeResumeFile = findCodexResumeFile(prevSessionId);
|
|
1557
|
-
client.clearSession();
|
|
1381
|
+
await disposeBackend();
|
|
1558
1382
|
session.clearSessionId();
|
|
1559
|
-
wasCreated = false;
|
|
1560
1383
|
currentModeHash = null;
|
|
1561
1384
|
pending = message;
|
|
1562
1385
|
permissionHandler.reset();
|
|
1563
1386
|
selectionHandler.reset();
|
|
1564
|
-
|
|
1565
|
-
diffProcessor.reset();
|
|
1566
|
-
session.onThinkingChange(false);
|
|
1387
|
+
resetTurnState();
|
|
1567
1388
|
continue;
|
|
1568
1389
|
}
|
|
1569
1390
|
currentModeHash = message.hash;
|
|
1570
1391
|
readyAlreadySent = false;
|
|
1571
1392
|
messageBuffer.addMessage(message.message, "user");
|
|
1393
|
+
const turnSignal = abortController.signal;
|
|
1572
1394
|
try {
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
case "read-only":
|
|
1578
|
-
return "never";
|
|
1579
|
-
case "safe-yolo":
|
|
1580
|
-
return "on-failure";
|
|
1581
|
-
case "yolo":
|
|
1582
|
-
return "on-failure";
|
|
1583
|
-
case "bypassPermissions":
|
|
1584
|
-
return "on-failure";
|
|
1585
|
-
case "acceptEdits":
|
|
1586
|
-
return "on-request";
|
|
1587
|
-
case "plan":
|
|
1588
|
-
return "untrusted";
|
|
1589
|
-
default:
|
|
1590
|
-
return "untrusted";
|
|
1591
|
-
}
|
|
1592
|
-
})();
|
|
1593
|
-
const sandbox = (() => {
|
|
1594
|
-
switch (message.mode.permissionMode) {
|
|
1595
|
-
case "default":
|
|
1596
|
-
return "workspace-write";
|
|
1597
|
-
case "read-only":
|
|
1598
|
-
return "read-only";
|
|
1599
|
-
case "safe-yolo":
|
|
1600
|
-
return "workspace-write";
|
|
1601
|
-
case "yolo":
|
|
1602
|
-
return "danger-full-access";
|
|
1603
|
-
case "bypassPermissions":
|
|
1604
|
-
return "danger-full-access";
|
|
1605
|
-
case "acceptEdits":
|
|
1606
|
-
return "workspace-write";
|
|
1607
|
-
case "plan":
|
|
1608
|
-
return "workspace-write";
|
|
1609
|
-
default:
|
|
1610
|
-
return "workspace-write";
|
|
1611
|
-
}
|
|
1612
|
-
})();
|
|
1613
|
-
if (!wasCreated) {
|
|
1614
|
-
const startConfig = {
|
|
1615
|
-
prompt: message.message,
|
|
1616
|
-
sandbox,
|
|
1617
|
-
"approval-policy": approvalPolicy,
|
|
1618
|
-
config: {}
|
|
1619
|
-
};
|
|
1620
|
-
if (message.mode.model) {
|
|
1621
|
-
startConfig.model = message.mode.model;
|
|
1622
|
-
}
|
|
1623
|
-
let resumeFile = null;
|
|
1624
|
-
if (pendingModeResumeFile) {
|
|
1625
|
-
resumeFile = pendingModeResumeFile;
|
|
1626
|
-
pendingModeResumeFile = null;
|
|
1627
|
-
} else if (storedSessionIdForResume) {
|
|
1628
|
-
resumeFile = findCodexResumeFile(storedSessionIdForResume);
|
|
1629
|
-
storedSessionIdForResume = null;
|
|
1630
|
-
} else if (session.sessionId) {
|
|
1631
|
-
resumeFile = findCodexResumeFile(session.sessionId);
|
|
1632
|
-
}
|
|
1633
|
-
if (resumeFile) {
|
|
1634
|
-
startConfig.config = {
|
|
1635
|
-
...startConfig.config || {},
|
|
1636
|
-
experimental_resume: resumeFile
|
|
1637
|
-
};
|
|
1638
|
-
}
|
|
1639
|
-
await client.startSession(startConfig, { signal: abortController.signal });
|
|
1640
|
-
const currentSessionId = client.getSessionId();
|
|
1641
|
-
if (currentSessionId) {
|
|
1642
|
-
session.onSessionFound(currentSessionId);
|
|
1643
|
-
}
|
|
1644
|
-
wasCreated = true;
|
|
1645
|
-
} else {
|
|
1646
|
-
await client.continueSession(message.message, { signal: abortController.signal });
|
|
1395
|
+
turnInFlight = true;
|
|
1396
|
+
const activeBackend = backend ?? createBackend(message.mode);
|
|
1397
|
+
if (!activeBackend) {
|
|
1398
|
+
throw new Error("Failed to create Codex ACP backend");
|
|
1647
1399
|
}
|
|
1400
|
+
if (!acpSessionId) {
|
|
1401
|
+
const { sessionId } = await activeBackend.startSession();
|
|
1402
|
+
acpSessionId = sessionId;
|
|
1403
|
+
wasSessionCreated = true;
|
|
1404
|
+
session.onSessionFound(sessionId);
|
|
1405
|
+
}
|
|
1406
|
+
session.onThinkingChange(true);
|
|
1407
|
+
await activeBackend.sendPrompt(acpSessionId, message.message);
|
|
1408
|
+
await waitForResponseCompleteWithAbort(activeBackend, turnSignal);
|
|
1409
|
+
reasoningProcessor.completeCurrent();
|
|
1648
1410
|
} catch (error) {
|
|
1649
|
-
api.logger.warn("Error in codex session:", error);
|
|
1411
|
+
api.logger.warn("Error in codex ACP session:", error);
|
|
1650
1412
|
const isAbortError = error instanceof Error && error.name === "AbortError";
|
|
1651
|
-
const isExpectedInterruption = isAbortError ||
|
|
1652
|
-
if (
|
|
1413
|
+
const isExpectedInterruption = isAbortError || turnSignal.aborted || shouldExit || exitReason === "switch";
|
|
1414
|
+
if (exitReason === "switch") {
|
|
1415
|
+
messageBuffer.addMessage("Switching to local mode...", "status");
|
|
1416
|
+
session.runtimeSession.sendSessionEvent({ type: "message", message: "Switching to local mode..." });
|
|
1417
|
+
} else if (isExpectedInterruption) {
|
|
1418
|
+
session.runtimeSession.sendCodexMessage({
|
|
1419
|
+
type: "turn_aborted",
|
|
1420
|
+
id: node_crypto.randomUUID()
|
|
1421
|
+
});
|
|
1653
1422
|
messageBuffer.addMessage("Aborted by user", "status");
|
|
1654
1423
|
session.runtimeSession.sendSessionEvent({ type: "message", message: "Aborted by user" });
|
|
1655
1424
|
} else {
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1425
|
+
const errorMessage = normalizeCodexBackendError(error);
|
|
1426
|
+
messageBuffer.addMessage(errorMessage, "status");
|
|
1427
|
+
session.runtimeSession.sendSessionEvent({ type: "message", message: errorMessage });
|
|
1428
|
+
await disposeBackend();
|
|
1429
|
+
session.clearSessionId();
|
|
1661
1430
|
}
|
|
1662
1431
|
} finally {
|
|
1432
|
+
turnInFlight = false;
|
|
1433
|
+
emitFinalAssistantMessage();
|
|
1434
|
+
if (!shouldExit) {
|
|
1435
|
+
session.runtimeSession.sendCodexMessage({
|
|
1436
|
+
type: "task_complete",
|
|
1437
|
+
id: node_crypto.randomUUID()
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1663
1440
|
permissionHandler.reset();
|
|
1664
1441
|
selectionHandler.reset();
|
|
1665
|
-
|
|
1666
|
-
diffProcessor.reset();
|
|
1667
|
-
session.onThinkingChange(false);
|
|
1442
|
+
resetTurnState();
|
|
1668
1443
|
if (shouldEmitCodexReadyAfterWait({
|
|
1669
1444
|
pending: Boolean(pending),
|
|
1670
1445
|
queueSize: session.queue.size(),
|
|
@@ -1677,15 +1452,7 @@ async function codexRemoteLauncher(session) {
|
|
|
1677
1452
|
}
|
|
1678
1453
|
}
|
|
1679
1454
|
} finally {
|
|
1680
|
-
|
|
1681
|
-
client.setPermissionHandler(null);
|
|
1682
|
-
client.setSelectionHandler(null);
|
|
1683
|
-
if (exitReason === "switch") {
|
|
1684
|
-
api.logger.debug("[Codex] Remote launcher exiting via switch; waiting for MCP transport disconnect");
|
|
1685
|
-
await client.disconnect();
|
|
1686
|
-
} else {
|
|
1687
|
-
await client.forceCloseSession();
|
|
1688
|
-
}
|
|
1455
|
+
await disposeBackend();
|
|
1689
1456
|
permissionHandler.reset();
|
|
1690
1457
|
selectionHandler.reset();
|
|
1691
1458
|
session.setPendingInteractionSuperseder(null);
|
|
@@ -1711,20 +1478,21 @@ async function codexRemoteLauncher(session) {
|
|
|
1711
1478
|
}
|
|
1712
1479
|
messageBuffer.clear();
|
|
1713
1480
|
}
|
|
1714
|
-
api.logger.debug(`[Codex]
|
|
1481
|
+
api.logger.debug(`[Codex] ACP remote launcher returning: ${exitReason || "exit"}`);
|
|
1715
1482
|
return exitReason || "exit";
|
|
1716
1483
|
}
|
|
1717
1484
|
|
|
1718
1485
|
async function codexLoop(opts) {
|
|
1719
1486
|
let mode = opts.startingMode ?? "local";
|
|
1720
|
-
opts.session.onModeChange(mode);
|
|
1487
|
+
await opts.session.onModeChange(mode);
|
|
1721
1488
|
while (true) {
|
|
1722
1489
|
api.logger.debug(`[codex-loop] Iteration with mode: ${mode}`);
|
|
1723
1490
|
if (mode === "local") {
|
|
1724
1491
|
const result = await codexLocalLauncher(opts.session);
|
|
1725
1492
|
if (result.type === "switch") {
|
|
1493
|
+
opts.session.clearLocalModePin();
|
|
1726
1494
|
mode = "remote";
|
|
1727
|
-
opts.session.onModeChange(mode);
|
|
1495
|
+
await opts.session.onModeChange(mode);
|
|
1728
1496
|
continue;
|
|
1729
1497
|
}
|
|
1730
1498
|
return result.code;
|
|
@@ -1732,13 +1500,53 @@ async function codexLoop(opts) {
|
|
|
1732
1500
|
const reason = await codexRemoteLauncher(opts.session);
|
|
1733
1501
|
if (reason === "switch") {
|
|
1734
1502
|
mode = "local";
|
|
1735
|
-
opts.session.onModeChange(mode);
|
|
1503
|
+
await opts.session.onModeChange(mode);
|
|
1736
1504
|
continue;
|
|
1737
1505
|
}
|
|
1738
1506
|
return 0;
|
|
1739
1507
|
}
|
|
1740
1508
|
}
|
|
1741
1509
|
|
|
1510
|
+
function supportsAgentStateUpdateEvents(sessionClient) {
|
|
1511
|
+
return typeof sessionClient.once === "function" && typeof sessionClient.off === "function";
|
|
1512
|
+
}
|
|
1513
|
+
async function syncControlledByUserState(sessionClient, controlledByUser) {
|
|
1514
|
+
if (!supportsAgentStateUpdateEvents(sessionClient)) {
|
|
1515
|
+
sessionClient.updateAgentState((currentState) => ({
|
|
1516
|
+
...currentState,
|
|
1517
|
+
controlledByUser
|
|
1518
|
+
}));
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
await new Promise((resolve) => {
|
|
1522
|
+
let settled = false;
|
|
1523
|
+
const handleUpdated = () => {
|
|
1524
|
+
if (settled) {
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
settled = true;
|
|
1528
|
+
clearTimeout(timeout);
|
|
1529
|
+
sessionClient.off("agent-state-updated", handleUpdated);
|
|
1530
|
+
resolve();
|
|
1531
|
+
};
|
|
1532
|
+
const timeout = setTimeout(() => {
|
|
1533
|
+
if (settled) {
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
settled = true;
|
|
1537
|
+
sessionClient.off("agent-state-updated", handleUpdated);
|
|
1538
|
+
resolve();
|
|
1539
|
+
}, 1500);
|
|
1540
|
+
sessionClient.once("agent-state-updated", handleUpdated);
|
|
1541
|
+
sessionClient.updateAgentState((currentState) => ({
|
|
1542
|
+
...currentState,
|
|
1543
|
+
controlledByUser
|
|
1544
|
+
}));
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
function shouldSupersedeCodexPendingInteractions(opts) {
|
|
1548
|
+
return (opts.currentMode ?? opts.startingMode) === "remote";
|
|
1549
|
+
}
|
|
1742
1550
|
async function runCodex(opts) {
|
|
1743
1551
|
const sessionTag = node_crypto.randomUUID();
|
|
1744
1552
|
api.connectionState.setBackend("Codex");
|
|
@@ -1762,7 +1570,15 @@ async function runCodex(opts) {
|
|
|
1762
1570
|
machineId,
|
|
1763
1571
|
startedBy: opts.startedBy
|
|
1764
1572
|
});
|
|
1765
|
-
|
|
1573
|
+
let response = null;
|
|
1574
|
+
try {
|
|
1575
|
+
response = await api$1.getOrCreateSession({ tag: sessionTag, metadata, state });
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
if (!api.isAuthenticationRequiredError(error)) {
|
|
1578
|
+
throw error;
|
|
1579
|
+
}
|
|
1580
|
+
api.logger.debug("[codex] Falling back to offline session stub after authentication failure");
|
|
1581
|
+
}
|
|
1766
1582
|
let sessionClient;
|
|
1767
1583
|
let codexSession = null;
|
|
1768
1584
|
const { session: initialSession, reconnectionHandle } = BaseReasoningProcessor.setupOfflineReconnection({
|
|
@@ -1804,7 +1620,11 @@ async function runCodex(opts) {
|
|
|
1804
1620
|
messageModel = message.meta.model || void 0;
|
|
1805
1621
|
currentModel = messageModel;
|
|
1806
1622
|
}
|
|
1807
|
-
if (
|
|
1623
|
+
if (shouldSupersedeCodexPendingInteractions({
|
|
1624
|
+
startingMode: opts.startingMode,
|
|
1625
|
+
currentMode: codexSession?.mode
|
|
1626
|
+
})) {
|
|
1627
|
+
api.logger.debug("[codex] Incoming user message superseding active remote interaction");
|
|
1808
1628
|
codexSession?.supersedePendingInteractions(BaseReasoningProcessor.INTERACTION_SUPERSEDED_ERROR);
|
|
1809
1629
|
}
|
|
1810
1630
|
messageQueue.push(message.content.text, {
|
|
@@ -1821,12 +1641,9 @@ async function runCodex(opts) {
|
|
|
1821
1641
|
mode: opts.startingMode ?? "local",
|
|
1822
1642
|
messageQueue,
|
|
1823
1643
|
codexArgs: opts.codexArgs,
|
|
1824
|
-
onModeChange: (mode) => {
|
|
1644
|
+
onModeChange: async (mode) => {
|
|
1825
1645
|
sessionClient.sendSessionEvent({ type: "switch", mode });
|
|
1826
|
-
sessionClient
|
|
1827
|
-
...currentState,
|
|
1828
|
-
controlledByUser: mode === "local"
|
|
1829
|
-
}));
|
|
1646
|
+
await syncControlledByUserState(sessionClient, mode === "local");
|
|
1830
1647
|
}
|
|
1831
1648
|
});
|
|
1832
1649
|
try {
|
|
@@ -1846,3 +1663,6 @@ async function runCodex(opts) {
|
|
|
1846
1663
|
}
|
|
1847
1664
|
|
|
1848
1665
|
exports.runCodex = runCodex;
|
|
1666
|
+
exports.shouldSupersedeCodexPendingInteractions = shouldSupersedeCodexPendingInteractions;
|
|
1667
|
+
exports.supportsAgentStateUpdateEvents = supportsAgentStateUpdateEvents;
|
|
1668
|
+
exports.syncControlledByUserState = syncControlledByUserState;
|