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