very-happy-cli 0.2.174 → 0.2.176
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/{AcpBackend-MMfGUkmp.cjs → AcpBackend-9_ZI1Bv3.cjs} +3 -3
- package/dist/{AcpBackend-CS8TSaaH.mjs → AcpBackend-COp6D8gl.mjs} +3 -3
- package/dist/{AcpSessionManager-C2MqOirs.cjs → AcpSessionManager-CjdJLofq.cjs} +1 -1
- package/dist/{AcpSessionManager-y1Iy1mzc.mjs → AcpSessionManager-DykjzBM4.mjs} +1 -1
- package/dist/{BasePermissionHandler-BiWdGyL1.mjs → BasePermissionHandler-ChMkmsGD.mjs} +1 -1
- package/dist/{BasePermissionHandler-CJQPjMEa.cjs → BasePermissionHandler-DPvLN7aR.cjs} +1 -1
- package/dist/{BaseReasoningProcessor-BY9a3RTU.cjs → BaseReasoningProcessor-Dnf16mtJ.cjs} +1 -1
- package/dist/{BaseReasoningProcessor-2sLuoRYw.mjs → BaseReasoningProcessor-pjvLSTQF.mjs} +1 -1
- package/dist/{agentHome-BeAq0X3o.cjs → agentHome-BKi7tsE9.cjs} +4 -4
- package/dist/{agentHome-CK6SbAVu.mjs → agentHome-D61ZMLXM.mjs} +4 -4
- package/dist/{api-Cy_Mbe8W.cjs → api-Buc_ejoA.cjs} +10 -10
- package/dist/{api-rOLB1-sV.mjs → api-CbwDDXJt.mjs} +10 -10
- package/dist/{apiMachine-A9n14NN1.cjs → apiMachine--PXoXwKq.cjs} +14 -14
- package/dist/{apiMachine-VBkOChth.mjs → apiMachine-CS7t3Rg-.mjs} +15 -15
- package/dist/{attachmentContent-CSGb8uDJ.mjs → attachmentContent-BQYc8KpE.mjs} +2 -2
- package/dist/{attachmentContent-ULnsMsIL.cjs → attachmentContent-CFzzTeV9.cjs} +2 -2
- package/dist/{attachmentLimits-BLbji11b.mjs → attachmentLimits-Di1Z93lN.mjs} +1 -1
- package/dist/{attachmentLimits-D6bT2jU1.cjs → attachmentLimits-DtFqqiwR.cjs} +1 -1
- package/dist/{auth-CiZfkTkd.mjs → auth-B3xMoveE.mjs} +5 -5
- package/dist/{auth-EnJQu_vf.mjs → auth-CBkmRIGw.mjs} +8 -8
- package/dist/{auth-BD2z4MCH.cjs → auth-CpO3ZUMz.cjs} +5 -5
- package/dist/{auth--YSQ1-dG.cjs → auth-LPvsVriN.cjs} +8 -8
- package/dist/{auto-D_hKBFAY.mjs → auto-DHGch_zK.mjs} +16 -16
- package/dist/{auto-CobMGmFM.cjs → auto-Dm3EIKT7.cjs} +15 -15
- package/dist/{bootstrap-BYrt5zUv.cjs → bootstrap-BboDz_w2.cjs} +2 -2
- package/dist/{bootstrap-D1RRCJdQ.mjs → bootstrap-CxuBAkJU.mjs} +2 -2
- package/dist/{browser-IVfX6PQT.mjs → browser-BvARP-YR.mjs} +1 -1
- package/dist/{browser-i45hmvWw.cjs → browser-GQZJxD2n.cjs} +1 -1
- package/dist/{claudeLocal-DnPoeXP2.mjs → claudeLocal-B9EhFmD7.mjs} +6 -6
- package/dist/{claudeLocal-Cg8Qn4tF.cjs → claudeLocal-D-mujSX1.cjs} +5 -5
- package/dist/{claudeLocalLauncher-BAavVhU8.mjs → claudeLocalLauncher-Bdl-9aU_.mjs} +11 -11
- package/dist/{claudeLocalLauncher-CFlsscqX.cjs → claudeLocalLauncher-Tbrx6TXF.cjs} +10 -10
- package/dist/{claudeSessionFork-bp8LeRZ9.mjs → claudeSessionFork-D_DKvDKE.mjs} +1 -1
- package/dist/{claudeSessionFork-DmwPXMmz.cjs → claudeSessionFork-sOz97qqW.cjs} +1 -1
- package/dist/{client-DbqP92Fy.mjs → client-Cg6ABRe-.mjs} +3 -3
- package/dist/{client-QtWC3yii.cjs → client-D7sSU04O.cjs} +3 -3
- package/dist/{client-B6H6ZROd.cjs → client-D9pEOhn4.cjs} +2 -2
- package/dist/{client-Bi1DOVy9.mjs → client-Ryu9fllj.mjs} +2 -2
- package/dist/codex/happyMcpStdioBridge.cjs +15 -15
- package/dist/codex/happyMcpStdioBridge.mjs +15 -15
- package/dist/{codexAppServerClient-pnCpJlD8.mjs → codexAppServerClient-C70pJ-BB.mjs} +432 -48
- package/dist/{codexAppServerClient-BWB1267F.cjs → codexAppServerClient-DsIUkVnX.cjs} +428 -44
- package/dist/{codexCommand-u4m_X8VA.cjs → codexCommand-Aycux-B7.cjs} +162 -60
- package/dist/{codexCommand-BmtdCu-D.mjs → codexCommand-DWbA3pJN.mjs} +163 -61
- package/dist/{config-BaJCpLzN.cjs → config-BcL0bxO5.cjs} +3 -3
- package/dist/{config-Czc1tQDn.mjs → config-Bztmzu8z.mjs} +3 -3
- package/dist/{configuration-zOfO2qdB.mjs → configuration-BAwOtWFY.mjs} +1 -1
- package/dist/{configuration-Btwt68O2.cjs → configuration-CpBrilxV.cjs} +1 -1
- package/dist/{connect-DQaDa6i7.cjs → connect-BBNCMTi8.cjs} +13 -13
- package/dist/{connect-D21OhdsD.mjs → connect-BeWQ4MMA.mjs} +13 -13
- package/dist/{context-BknomZTq.cjs → context-Hk0j2Y9-.cjs} +1 -1
- package/dist/{context-BTX3YYdt.mjs → context-j114QJre.mjs} +1 -1
- package/dist/{controlClient-CbmWgWJW.mjs → controlClient-00KYAjsI.mjs} +3 -3
- package/dist/{controlClient-CEpaptre.cjs → controlClient-bs_YqwDl.cjs} +3 -3
- package/dist/{createSessionMetadata-Cgfcg0WZ.cjs → createSessionMetadata-D4Sj6H95.cjs} +2 -2
- package/dist/{createSessionMetadata-CM7ErnPh.mjs → createSessionMetadata-xlycGog5.mjs} +2 -2
- package/dist/{defaultPermissionMode-CmgCDVpL.mjs → defaultPermissionMode-B97ox9p1.mjs} +2 -2
- package/dist/{defaultPermissionMode-DqdkzYUY.cjs → defaultPermissionMode-CMsiaRzr.cjs} +2 -2
- package/dist/{deterministicJson-B3V5Ss2l.cjs → deterministicJson-aHxtYLha.cjs} +9 -9
- package/dist/{deterministicJson-DVvfcibH.mjs → deterministicJson-ucyv8j6b.mjs} +9 -9
- package/dist/{doctor-C_Xfs8Bs.cjs → doctor-BXkNXcok.cjs} +7 -7
- package/dist/{doctor-CidhGBde.mjs → doctor-D554c6Bg.mjs} +7 -7
- package/dist/{downloadTarget-C-18yOBz.mjs → downloadTarget-2_zMvt2N.mjs} +6 -6
- package/dist/{downloadTarget-DJKG6wVj.cjs → downloadTarget-CmdlljZW.cjs} +6 -6
- package/dist/{ensureDaemonRunning-Dk1yo6aK.cjs → ensureDaemonRunning-CY7iC--U.cjs} +5 -5
- package/dist/{ensureDaemonRunning-CWo9yDAC.mjs → ensureDaemonRunning-DJlziY-m.mjs} +5 -5
- package/dist/{handleResumeCommand-ApqydtLf.mjs → handleResumeCommand-CNTnXBXA.mjs} +5 -5
- package/dist/{handleResumeCommand-DvFAI68O.cjs → handleResumeCommand-lSVnvTNC.cjs} +5 -5
- package/dist/{index-D0IWz-WE.mjs → index-59hR7nVz.mjs} +3 -3
- package/dist/{index-CMeZ7WcS.mjs → index-CxSLrTPA.mjs} +4 -4
- package/dist/{index-boE-d8CM.mjs → index-DMy3OCBe.mjs} +8 -1
- package/dist/{index-BVC5vBcc.mjs → index-DY3I8j0W.mjs} +37 -37
- package/dist/{index-BZdhwBKa.cjs → index-De8L5lFK.cjs} +8 -0
- package/dist/{index-azi2eHdp.cjs → index-DhARRePz.cjs} +4 -4
- package/dist/{index-CjFzrcin.cjs → index-MwTVbeGW.cjs} +3 -3
- package/dist/{index-CXCoTvVd.cjs → index-jeD70GGH.cjs} +36 -36
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{install-DayuanHz.cjs → install-DXUEtGfU.cjs} +3 -3
- package/dist/{install-BOPmEeSo.mjs → install-DytIJ_Ia.mjs} +3 -3
- package/dist/lib.cjs +12 -12
- package/dist/lib.mjs +12 -12
- package/dist/{lidState-Bv862v_R.mjs → lidState-CduPmrM0.mjs} +1 -1
- package/dist/{lidState-7uLoa43a.cjs → lidState-UasMvPzw.cjs} +1 -1
- package/dist/{localHappyAgentAuth-CfILduPd.cjs → localHappyAgentAuth-CA02WOny.cjs} +1 -1
- package/dist/{localHappyAgentAuth-CLP3CBMK.mjs → localHappyAgentAuth-DL8JjHTV.mjs} +1 -1
- package/dist/{logger-CUxbqfrp.mjs → logger-CihCDWDw.mjs} +2 -2
- package/dist/{logger-XjIS0lU7.cjs → logger-zoO54Zsy.cjs} +2 -2
- package/dist/{machineMetadata-DTxYqMdE.cjs → machineMetadata-BAKVtr-k.cjs} +3 -3
- package/dist/{machineMetadata-QfdvKIrL.mjs → machineMetadata-DEZcQ_CQ.mjs} +3 -3
- package/dist/{main-BpC52KlL.cjs → main-BIvxyX71.cjs} +44 -44
- package/dist/{main-DcfU9Axf.mjs → main-DyJfNlL_.mjs} +44 -44
- package/dist/{manager-BEsI4qt3.mjs → manager-2Il8XJVq.mjs} +1 -1
- package/dist/{mcp-EEwHzYYU.cjs → mcp-Dd11vHij.cjs} +14 -14
- package/dist/{mcp-BhUNcpoX.mjs → mcp-yoIdGe6C.mjs} +14 -14
- package/dist/{mirrorProtocol-BoBu65cj.mjs → mirrorProtocol-BukXB5s3.mjs} +3 -3
- package/dist/{mirrorProtocol-WfE5yd3v.cjs → mirrorProtocol-D-hF_SE5.cjs} +3 -3
- package/dist/{package-DFQUF2in.cjs → package-A7zOGx8B.cjs} +7 -7
- package/dist/{package-DkI3yDe0.mjs → package-ayqGDKSp.mjs} +7 -7
- package/dist/{peerTools-zfe59JAw.mjs → peerTools-DDMSViPu.mjs} +4 -4
- package/dist/{peerTools-DbI6tEk1.cjs → peerTools-dPc9hRC5.cjs} +4 -4
- package/dist/{persistence-BO7AS2NK.cjs → persistence-Cih2AWml.cjs} +2 -2
- package/dist/{persistence-CBToZ0R6.mjs → persistence-Dy3lmall.mjs} +2 -2
- package/dist/{piRunnerArgs-CICVlVem.cjs → piRunnerArgs-BuJgnUI5.cjs} +1 -1
- package/dist/{piRunnerArgs-C2us7__q.mjs → piRunnerArgs-D3z3-1VJ.mjs} +1 -1
- package/dist/{piRuntime-CTezrTNw.cjs → piRuntime-B-SPcWIr.cjs} +1 -1
- package/dist/{piRuntime-BYHZYbw6.mjs → piRuntime-kVGue-_5.mjs} +1 -1
- package/dist/{piTerminal-CeiWEB-T.cjs → piTerminal-DSxK5aAm.cjs} +16 -16
- package/dist/{piTerminal-YCRkoVWF.mjs → piTerminal-aEtVdCJ2.mjs} +16 -16
- package/dist/{rateLimitWindows-U7NsRvIa.cjs → rateLimitWindows-82izLQy8.cjs} +1 -1
- package/dist/{rateLimitWindows-i2-530Zt.mjs → rateLimitWindows-E5DRjfE6.mjs} +1 -1
- package/dist/{registerCommonHandlers-C_KbP5Mq.mjs → registerCommonHandlers-Bm7CMqMv.mjs} +2 -2
- package/dist/{registerCommonHandlers-DTeuk3Hw.cjs → registerCommonHandlers-CqO075-X.cjs} +2 -2
- package/dist/{registerKillSessionHandler-B7zpuzvS.mjs → registerKillSessionHandler-D02OY4vi.mjs} +6 -6
- package/dist/{registerKillSessionHandler-B_FqygY4.cjs → registerKillSessionHandler-WQra3T9B.cjs} +6 -6
- package/dist/{remoteSessionClient-Bk1Qzb5I.mjs → remoteSessionClient-Buh6NejV.mjs} +5 -5
- package/dist/{remoteSessionClient-D79ci1L8.cjs → remoteSessionClient-f3DYorl8.cjs} +5 -5
- package/dist/{reportProgressTool-oJMMqzKb.cjs → reportProgressTool-C2gn_HWD.cjs} +2 -2
- package/dist/{reportProgressTool-DujvMncK.mjs → reportProgressTool-C5aSoKYX.mjs} +2 -2
- package/dist/{run-CyiUjBZK.cjs → run-CDEmq6Zs.cjs} +34 -34
- package/dist/{run-DVE2Gq-E.mjs → run-Drxogp0j.mjs} +33 -33
- package/dist/{runClaude-BcpBew2h.mjs → runClaude-BF6U2Js7.mjs} +21 -21
- package/dist/{runClaude-CmAHAR3N.cjs → runClaude-yEcZTCt_.cjs} +21 -21
- package/dist/{runClaudeDeps-C2fTmXoo.mjs → runClaudeDeps-Bl4ndq7N.mjs} +40 -40
- package/dist/{runClaudeDeps-Z_RVvcuG.cjs → runClaudeDeps-CnNNFjlI.cjs} +35 -35
- package/dist/{runGemini-BYmVfC3b.mjs → runGemini-C3wt7iv7.mjs} +32 -32
- package/dist/{runGemini-Bv4nVBfj.cjs → runGemini-DDVDRNg4.cjs} +32 -32
- package/dist/{runOpenClaw-D5suaSgY.cjs → runOpenClaw-BzlVA_9Q.cjs} +18 -18
- package/dist/{runOpenClaw-msp2nzW8.mjs → runOpenClaw-CY6YHAvj.mjs} +18 -18
- package/dist/{sandbox-CSnsdXId.cjs → sandbox-0UNHR3hP.cjs} +4 -4
- package/dist/{sandbox-CH7xLX1-.mjs → sandbox-CX_wLwpq.mjs} +4 -4
- package/dist/selfCheck-ClVv8NK3.mjs +84 -0
- package/dist/{selfCheck-B4mCW5Em.cjs → selfCheck-FNwMFY8p.cjs} +34 -34
- package/dist/{send-DzRwXl8a.cjs → send-BQkl25g0.cjs} +8 -8
- package/dist/{send-lrV7t7dr.mjs → send-CpEGYict.mjs} +8 -8
- package/dist/{server-BAwRC79P.cjs → server-BodFZ5sW.cjs} +6 -6
- package/dist/{server-DBHPW2gs.mjs → server-DnINX_Lm.mjs} +4 -4
- package/dist/{sessionLock-ClIuWFru.cjs → sessionLock-DOj6mMXn.cjs} +3 -3
- package/dist/{sessionLock-D_-T15Y-.mjs → sessionLock-Ddn-fhZ0.mjs} +3 -3
- package/dist/{sessionModeFile-BP1s4On2.mjs → sessionModeFile-DDbonFR_.mjs} +1 -1
- package/dist/{sessionModeFile-BFejyKn5.cjs → sessionModeFile-DVukoSp7.cjs} +1 -1
- package/dist/{sessionOps-DfAtoDD5.mjs → sessionOps-B1vQ25GS.mjs} +4 -4
- package/dist/{sessionOps-C7wI8Ll6.cjs → sessionOps-Nryt1DZt.cjs} +4 -4
- package/dist/{sessionScanner-BJFPUwar.cjs → sessionScanner-D1h-cdte.cjs} +3 -3
- package/dist/{sessionScanner-1-ANJdrm.mjs → sessionScanner-DCr3ofhR.mjs} +3 -3
- package/dist/{sessions-Bi13Rn0K.cjs → sessions-D2HsTbLf.cjs} +9 -9
- package/dist/{sessions-Dp_X8vP2.mjs → sessions-DpAN6Ft9.mjs} +9 -9
- package/dist/{setupOfflineReconnection-BJCXuFad.cjs → setupOfflineReconnection-B9Tw1Ouj.cjs} +2 -2
- package/dist/{setupOfflineReconnection-D51KXWAP.mjs → setupOfflineReconnection-DQHEp330.mjs} +2 -2
- package/dist/{spawn-DeXPOMMC.mjs → spawn-CNjm1yOi.mjs} +14 -14
- package/dist/{spawn-BiYYv0J7.cjs → spawn-CvDZ38hi.cjs} +13 -13
- package/dist/{spawnAgents-BhsbJ253.mjs → spawnAgents-BjtmjBvP.mjs} +1 -1
- package/dist/{spawnAgents-CgMKuOhZ.cjs → spawnAgents-CCjJifAW.cjs} +1 -1
- package/dist/{spawnHappyCLI-CYS23qZY.mjs → spawnHappyCLI-C8Vl56wB.mjs} +1 -1
- package/dist/{spawnHappyCLI-CrKdy_rv.cjs → spawnHappyCLI-ahi4j6Mh.cjs} +1 -1
- package/dist/{specialCommands-Dkg5wrXd.cjs → specialCommands-BpqnGrOP.cjs} +3 -3
- package/dist/{specialCommands-BbNlcS3G.mjs → specialCommands-DfVMGXTl.mjs} +3 -3
- package/dist/{startFileWatcher-CgH0PknF.mjs → startFileWatcher-DXJXY4AE.mjs} +1 -1
- package/dist/{startFileWatcher-BJ4vTfRp.cjs → startFileWatcher-Ddn3L0u3.cjs} +1 -1
- package/dist/{streamRelay-DqPFsCgx.cjs → streamRelay-Cx01enfe.cjs} +1 -1
- package/dist/{streamRelay-C8zI7OnR.mjs → streamRelay-_qCd873P.mjs} +1 -1
- package/dist/{sync-MTpgBfV2.mjs → sync-BKvgkhPs.mjs} +1 -1
- package/dist/{sync-CGVTRg-R.cjs → sync-BgZuthoS.cjs} +1 -1
- package/dist/{systemPrompt-DRCJdhNo.cjs → systemPrompt-C6O1cHUg.cjs} +2 -2
- package/dist/{systemPrompt-BMGFnaRU.mjs → systemPrompt-DuPem6c9.mjs} +2 -2
- package/dist/{teams-H_F3UZkh.mjs → teams-CffMBBfU.mjs} +6 -6
- package/dist/{teams-0d4XG2_h.cjs → teams-DifEhhmk.cjs} +6 -6
- package/dist/{time-7iVn8KLQ.mjs → time-DxCTiteK.mjs} +1 -1
- package/dist/{time-ChuhR6SU.cjs → time-s8CaneqW.cjs} +1 -1
- package/dist/{titleGenerator-CLdAiwN1.mjs → titleGenerator-B5T7QSzu.mjs} +1 -1
- package/dist/{titleGenerator-CozWnU-t.cjs → titleGenerator-CXr7t6cf.cjs} +2 -2
- package/dist/{todo-BeIYBXws.cjs → todo-B41GKMcI.cjs} +6 -6
- package/dist/{todo-7mtNKrxn.mjs → todo-DNO7s8td.mjs} +6 -6
- package/dist/{tools-BSlqfWAq.mjs → tools-697R1uqn.mjs} +1 -1
- package/dist/{tools-9hPR5-u8.cjs → tools-lotMyxIE.cjs} +1 -1
- package/dist/{uninstall-Bf5MardO.cjs → uninstall-CRu96len.cjs} +3 -3
- package/dist/{uninstall-Uei9RnVT.mjs → uninstall-CYO7EDjO.mjs} +3 -3
- package/package.json +7 -7
- package/dist/selfCheck-WbfDqlfQ.mjs +0 -84
|
@@ -1,12 +1,100 @@
|
|
|
1
|
-
import { execSync } from 'node:child_process';
|
|
2
|
-
import { spawn } from 'cross-spawn';
|
|
1
|
+
import { spawn, execSync } from 'node:child_process';
|
|
2
|
+
import { spawn as spawn$1 } from 'cross-spawn';
|
|
3
3
|
import { createInterface } from 'node:readline';
|
|
4
|
-
import { l as logger } from './logger-
|
|
4
|
+
import { l as logger } from './logger-CihCDWDw.mjs';
|
|
5
5
|
import { e as errorLogMetadata, c as contentLogMetadata } from './contentLogMetadata-BlWI0egu.mjs';
|
|
6
|
-
import { i as initializeSandbox,
|
|
7
|
-
import { p as packageJson } from './package-
|
|
8
|
-
import { s as shouldReadCodexRateLimits, i as isUnsupportedCodexMethodError } from './rateLimitWindows-
|
|
6
|
+
import { i as initializeSandbox, a as wrapForMcpTransport } from './manager-2Il8XJVq.mjs';
|
|
7
|
+
import { p as packageJson } from './package-ayqGDKSp.mjs';
|
|
8
|
+
import { s as shouldReadCodexRateLimits, i as isUnsupportedCodexMethodError } from './rateLimitWindows-E5DRjfE6.mjs';
|
|
9
9
|
|
|
10
|
+
const CODEX_HELPER_LABEL_MAX_CHARS = 80;
|
|
11
|
+
function codexHelperLabel(prompt) {
|
|
12
|
+
if (typeof prompt !== "string") return null;
|
|
13
|
+
const collapsed = prompt.replace(/\s+/g, " ").trim();
|
|
14
|
+
if (collapsed.length === 0) return null;
|
|
15
|
+
if (collapsed.length <= CODEX_HELPER_LABEL_MAX_CHARS) return collapsed;
|
|
16
|
+
const keep = CODEX_HELPER_LABEL_MAX_CHARS - 1;
|
|
17
|
+
const last = collapsed.charCodeAt(keep - 1);
|
|
18
|
+
const end = last >= 55296 && last <= 56319 ? keep - 1 : keep;
|
|
19
|
+
return `${collapsed.slice(0, end)}\u2026`;
|
|
20
|
+
}
|
|
21
|
+
function codexAgentPathSegments(agentPath) {
|
|
22
|
+
if (typeof agentPath !== "string") return [];
|
|
23
|
+
return agentPath.split("/").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
|
|
24
|
+
}
|
|
25
|
+
function isRootAgentPath(segments) {
|
|
26
|
+
return segments.length === 1 && segments[0] === "root";
|
|
27
|
+
}
|
|
28
|
+
function codexChildAnnouncements(item) {
|
|
29
|
+
if (item.type === "subAgentActivity") {
|
|
30
|
+
const threadId = item.agentThreadId;
|
|
31
|
+
if (typeof threadId !== "string" || threadId.length === 0) return [];
|
|
32
|
+
const segments = codexAgentPathSegments(item.agentPath);
|
|
33
|
+
if (isRootAgentPath(segments)) return [];
|
|
34
|
+
const leaf = segments.at(-1);
|
|
35
|
+
return [{ threadId, label: leaf && leaf.length > 0 ? leaf : null }];
|
|
36
|
+
}
|
|
37
|
+
if (item.type === "collabAgentToolCall") {
|
|
38
|
+
const receivers = Array.isArray(item.receiverThreadIds) ? item.receiverThreadIds.filter((id) => typeof id === "string" && id.length > 0) : [];
|
|
39
|
+
const label = item.tool === "spawnAgent" ? codexHelperLabel(item.prompt) : null;
|
|
40
|
+
return [...new Set(receivers)].map((threadId) => ({ threadId, label }));
|
|
41
|
+
}
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stopCodexProcess(proc, deps = {}) {
|
|
46
|
+
const platform = deps.platform ?? process.platform;
|
|
47
|
+
try {
|
|
48
|
+
proc.stdin?.end();
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
if (platform === "win32") return;
|
|
52
|
+
try {
|
|
53
|
+
proc.kill("SIGTERM");
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function killCodexProcessTree(proc, deps = {}) {
|
|
58
|
+
if (proc.exitCode != null || proc.signalCode != null) return;
|
|
59
|
+
const killRoot = () => {
|
|
60
|
+
try {
|
|
61
|
+
proc.kill("SIGKILL");
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const pid = proc.pid;
|
|
66
|
+
if (!pid || !("exitCode" in proc)) {
|
|
67
|
+
killRoot();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const platform = deps.platform ?? process.platform;
|
|
71
|
+
const spawn$1 = deps.spawn ?? ((command, args, options) => spawn(command, args, options));
|
|
72
|
+
if (platform === "win32") {
|
|
73
|
+
try {
|
|
74
|
+
const killer = spawn$1("taskkill", ["/pid", String(pid), "/t", "/f"], { stdio: "ignore", windowsHide: true });
|
|
75
|
+
killer.on("error", killRoot);
|
|
76
|
+
killer.on("exit", (code) => {
|
|
77
|
+
if (code !== 0) killRoot();
|
|
78
|
+
});
|
|
79
|
+
killer.unref();
|
|
80
|
+
} catch {
|
|
81
|
+
killRoot();
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const reaper = spawn$1("pkill", ["-KILL", "-P", String(pid)], { stdio: "ignore", windowsHide: true });
|
|
87
|
+
reaper.on("error", () => void 0);
|
|
88
|
+
reaper.unref();
|
|
89
|
+
} catch {
|
|
90
|
+
}
|
|
91
|
+
killRoot();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function jsonRpcErrorCodeOf(error) {
|
|
95
|
+
const code = error?.jsonRpcCode;
|
|
96
|
+
return typeof code === "number" ? code : null;
|
|
97
|
+
}
|
|
10
98
|
function isAppServerAvailable() {
|
|
11
99
|
try {
|
|
12
100
|
const version = execSync("codex --version", { encoding: "utf8", windowsHide: true }).trim();
|
|
@@ -89,7 +177,6 @@ class CodexAppServerClient {
|
|
|
89
177
|
_turnId = null;
|
|
90
178
|
threadDefaults = null;
|
|
91
179
|
// Turn completion tracking for the currently active sendTurnAndWait call.
|
|
92
|
-
// A completion event only resolves once we have seen task_started for this turn.
|
|
93
180
|
pendingTurnCompletion = null;
|
|
94
181
|
// Tracks in-flight interruptTurn() RPCs so sendTurnAndWait can wait for them
|
|
95
182
|
// before starting a new turn (prevents stale turn/interrupt from aborting the next turn).
|
|
@@ -99,6 +186,22 @@ class CodexAppServerClient {
|
|
|
99
186
|
idleRelease = null;
|
|
100
187
|
notificationProtocol = "unknown";
|
|
101
188
|
completedTurnIds = /* @__PURE__ */ new Set();
|
|
189
|
+
/** B-625: turns whose failure has already been forwarded, so a second failed frame is not. */
|
|
190
|
+
failedTurnIds = /* @__PURE__ */ new Set();
|
|
191
|
+
// B-625: the app-server speaks for every thread it hosts — the session's own and the
|
|
192
|
+
// children `spawn_agent` opens — so a lifecycle frame (turn/started, turn/completed,
|
|
193
|
+
// thread/status/changed, error, thread/closed) is the session's only when its `threadId`
|
|
194
|
+
// is ours; a frame naming no thread belongs to the thread it was asked on (Orca's rule,
|
|
195
|
+
// src/main/codex/codex-structured-turn-open-wait.ts). Before this, a child's
|
|
196
|
+
// `turn/completed` ended the parent's turn in the UI and a child's `turn/started`
|
|
197
|
+
// retargeted `turn/interrupt` at the child.
|
|
198
|
+
/** The own thread's last turn-ending `error` (willRetry=false), for a turn end that arrives without a verdict. */
|
|
199
|
+
lastTurnError = null;
|
|
200
|
+
/** Own thread reported idle/systemError while a turn was pending: Codex's `turn/completed`
|
|
201
|
+
* follows within a millisecond; this ends the turn if it does not (see armThreadStopGrace). */
|
|
202
|
+
threadStopGrace = null;
|
|
203
|
+
/** Last few hundred chars of the app-server's stderr: Codex's own last words when it dies. */
|
|
204
|
+
stderrRing = "";
|
|
102
205
|
rawFileChangesByItemId = /* @__PURE__ */ new Map();
|
|
103
206
|
// B-622: inactivity watchdog for the pending turn — re-armed by every
|
|
104
207
|
// notification, never a cap on how long a turn may run (see sendTurnAndWait).
|
|
@@ -106,6 +209,18 @@ class CodexAppServerClient {
|
|
|
106
209
|
// Approval / elicitation requests we are still answering: a human may take
|
|
107
210
|
// longer than the watchdog to decide, and that is not Codex going silent.
|
|
108
211
|
inflightServerRequests = 0;
|
|
212
|
+
// B-628: `turn/steer` — a message sent into the running turn (approach from Orca,
|
|
213
|
+
// src/main/codex/codex-structured-turn-start.ts). This app-server has no such
|
|
214
|
+
// method (-32601) until it is respawned; a refusal is per call (the turn it named
|
|
215
|
+
// ended or changed meanwhile, a review/compact turn).
|
|
216
|
+
steerUnsupported = false;
|
|
217
|
+
/** Steered inputs Codex has not echoed back as a `userMessage` item yet: clientId → turnId. */
|
|
218
|
+
steeredAwaitingEcho = /* @__PURE__ */ new Map();
|
|
219
|
+
/** Waiters for the pending turn to open (steerTurn); released when it opens or ends. */
|
|
220
|
+
turnOpenWaiters = [];
|
|
221
|
+
/** B-628: the names Codex gave the child threads it spawned (see codexChildThreads.ts). */
|
|
222
|
+
childThreadLabels = /* @__PURE__ */ new Map();
|
|
223
|
+
static MAX_CHILD_LABELS = 128;
|
|
109
224
|
// Handlers set by the consumer (runCodex.ts)
|
|
110
225
|
eventHandler = null;
|
|
111
226
|
approvalHandler = null;
|
|
@@ -167,8 +282,73 @@ class CodexAppServerClient {
|
|
|
167
282
|
const status = params?.turn?.status ?? params?.status ?? null;
|
|
168
283
|
return typeof status === "string" && status.length > 0 ? status : null;
|
|
169
284
|
}
|
|
285
|
+
extractThreadId(params) {
|
|
286
|
+
const threadId = params?.threadId ?? params?.thread_id ?? params?.thread?.id ?? null;
|
|
287
|
+
return typeof threadId === "string" && threadId.length > 0 ? threadId : null;
|
|
288
|
+
}
|
|
289
|
+
/** B-625: whether a lifecycle frame is about the session's own thread (see the field comment). */
|
|
290
|
+
isOwnThreadFrame(params) {
|
|
291
|
+
const threadId = this.extractThreadId(params);
|
|
292
|
+
return threadId === null || this._threadId === null || threadId === this._threadId;
|
|
293
|
+
}
|
|
294
|
+
/** B-628: the child thread a frame belongs to, or null for the session's own. */
|
|
295
|
+
childThreadIdOf(params) {
|
|
296
|
+
return this.isOwnThreadFrame(params) ? null : this.extractThreadId(params);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* B-628: what a child thread's item carries so the mapper files it under the child's
|
|
300
|
+
* card (`subagent` → the session protocol's sub-agent envelope, as a Claude Task's
|
|
301
|
+
* children are) instead of in the main conversation as the session's own work.
|
|
302
|
+
*/
|
|
303
|
+
childItemFields(params) {
|
|
304
|
+
const child = this.childThreadIdOf(params);
|
|
305
|
+
if (!child) return {};
|
|
306
|
+
const label = this.childThreadLabels.get(child);
|
|
307
|
+
return { subagent: child, ...label ? { subagent_label: label } : {} };
|
|
308
|
+
}
|
|
309
|
+
rememberChildLabels(item) {
|
|
310
|
+
for (const { threadId, label } of codexChildAnnouncements(item)) {
|
|
311
|
+
if (!label || this.childThreadLabels.has(threadId)) continue;
|
|
312
|
+
if (this.childThreadLabels.size >= CodexAppServerClient.MAX_CHILD_LABELS) {
|
|
313
|
+
const oldest = this.childThreadLabels.keys().next().value;
|
|
314
|
+
if (oldest !== void 0) this.childThreadLabels.delete(oldest);
|
|
315
|
+
}
|
|
316
|
+
this.childThreadLabels.set(threadId, label);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* B-628: steered inputs the ended turn never echoed. `report` = the turn did not complete
|
|
321
|
+
* (interrupted, failed, app-server gone, given up): Codex never read them, tell the runner.
|
|
322
|
+
* A turn that *completed* drains its pending input before it ends (core/src/session/turn.rs),
|
|
323
|
+
* so an unechoed input there is a codex that accepted the steer but echoes no `clientId` —
|
|
324
|
+
* forgotten silently, because a false「not delivered」on a message the model did read would
|
|
325
|
+
* be worse than today's silence.
|
|
326
|
+
*/
|
|
327
|
+
dropUnechoedSteers(turnId, report) {
|
|
328
|
+
const dropped = [];
|
|
329
|
+
for (const [clientId, steeredTurn] of this.steeredAwaitingEcho) {
|
|
330
|
+
if (turnId === null || steeredTurn === turnId) {
|
|
331
|
+
dropped.push(clientId);
|
|
332
|
+
this.steeredAwaitingEcho.delete(clientId);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (dropped.length === 0) return;
|
|
336
|
+
if (!report) {
|
|
337
|
+
logger.debug(`[CodexAppServer] ${dropped.length} steered message(s) completed without an echo; not reported`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
this.eventHandler?.({
|
|
341
|
+
type: "steer_dropped",
|
|
342
|
+
client_ids: dropped,
|
|
343
|
+
...turnId ? { turn_id: turnId } : {}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
/** Codex's last stderr lines, one line, for a message a person reads. */
|
|
347
|
+
stderrTail() {
|
|
348
|
+
return this.stderrRing.replace(/\x1b\[[0-9;]*m/g, "").replace(/\s+/g, " ").trim();
|
|
349
|
+
}
|
|
170
350
|
shouldHandleRawNotification(method) {
|
|
171
|
-
const isRawNotification = method === "thread/started" || method === "turn/started" || method === "turn/completed" || method === "thread/status/changed" || method === "thread/tokenUsage/updated" || method.startsWith("item/");
|
|
351
|
+
const isRawNotification = method === "thread/started" || method === "turn/started" || method === "turn/completed" || method === "thread/status/changed" || method === "thread/closed" || method === "thread/tokenUsage/updated" || method.startsWith("item/");
|
|
172
352
|
if (!isRawNotification) {
|
|
173
353
|
return false;
|
|
174
354
|
}
|
|
@@ -183,15 +363,21 @@ class CodexAppServerClient {
|
|
|
183
363
|
emitRawTurnCompletion(turnId, status, error, source) {
|
|
184
364
|
const aborted = status === "cancelled" || status === "canceled" || status === "aborted" || status === "interrupted";
|
|
185
365
|
const failed = status === "failed" || error !== void 0 && error !== null;
|
|
366
|
+
this.clearThreadStopGrace();
|
|
186
367
|
this.tryResolvePendingTurn(aborted, turnId, source);
|
|
187
368
|
this._turnId = null;
|
|
188
369
|
this.maybeRefreshRateLimits();
|
|
370
|
+
if (this.lastTurnError && (!turnId || !this.lastTurnError.turnId || this.lastTurnError.turnId === turnId)) {
|
|
371
|
+
this.lastTurnError = null;
|
|
372
|
+
}
|
|
189
373
|
if (turnId && this.completedTurnIds.has(turnId)) {
|
|
190
|
-
if (!failed) return;
|
|
374
|
+
if (!failed || this.failedTurnIds.has(turnId)) return;
|
|
191
375
|
}
|
|
192
376
|
if (turnId) {
|
|
193
377
|
this.completedTurnIds.add(turnId);
|
|
378
|
+
if (failed) this.failedTurnIds.add(turnId);
|
|
194
379
|
}
|
|
380
|
+
this.dropUnechoedSteers(turnId, aborted || failed);
|
|
195
381
|
if (aborted) {
|
|
196
382
|
this.eventHandler?.({
|
|
197
383
|
type: "turn_aborted",
|
|
@@ -213,6 +399,10 @@ class CodexAppServerClient {
|
|
|
213
399
|
return false;
|
|
214
400
|
}
|
|
215
401
|
if (method === "turn/started") {
|
|
402
|
+
if (!this.isOwnThreadFrame(params)) {
|
|
403
|
+
logger.debug(`[CodexAppServer] Child thread ${this.extractThreadId(params)} started a turn`);
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
216
406
|
const turnId = this.extractTurnId(params);
|
|
217
407
|
if (turnId) {
|
|
218
408
|
this._turnId = turnId;
|
|
@@ -225,6 +415,15 @@ class CodexAppServerClient {
|
|
|
225
415
|
return true;
|
|
226
416
|
}
|
|
227
417
|
if (method === "turn/completed") {
|
|
418
|
+
if (!this.isOwnThreadFrame(params)) {
|
|
419
|
+
logger.debug(`[CodexAppServer] Child thread ${this.extractThreadId(params)} ended a turn`);
|
|
420
|
+
this.eventHandler?.({
|
|
421
|
+
type: "subagent_turn_end",
|
|
422
|
+
...this.childItemFields(params),
|
|
423
|
+
status: this.extractTurnStatus(params)
|
|
424
|
+
});
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
228
427
|
this.emitRawTurnCompletion(
|
|
229
428
|
this.extractTurnId(params),
|
|
230
429
|
this.extractTurnStatus(params),
|
|
@@ -234,13 +433,28 @@ class CodexAppServerClient {
|
|
|
234
433
|
return true;
|
|
235
434
|
}
|
|
236
435
|
if (method === "thread/status/changed") {
|
|
436
|
+
if (!this.isOwnThreadFrame(params)) return true;
|
|
237
437
|
const statusType = params?.status?.type;
|
|
238
|
-
if (statusType === "idle" && this.pendingTurnCompletion) {
|
|
239
|
-
this.
|
|
438
|
+
if ((statusType === "idle" || statusType === "systemError") && this.pendingTurnCompletion?.opened) {
|
|
439
|
+
this.armThreadStopGrace(statusType);
|
|
440
|
+
} else if (statusType === "active") {
|
|
441
|
+
this.clearThreadStopGrace();
|
|
442
|
+
}
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
if (method === "thread/closed") {
|
|
446
|
+
if (this.isOwnThreadFrame(params) && this.pendingTurnCompletion) {
|
|
447
|
+
this.emitRawTurnCompletion(
|
|
448
|
+
this.pendingTurnCompletion.turnId ?? this._turnId,
|
|
449
|
+
"aborted",
|
|
450
|
+
{ message: "Codex closed the thread before the turn ended." },
|
|
451
|
+
method
|
|
452
|
+
);
|
|
240
453
|
}
|
|
241
454
|
return true;
|
|
242
455
|
}
|
|
243
456
|
if (method === "thread/tokenUsage/updated") {
|
|
457
|
+
if (!this.isOwnThreadFrame(params)) return true;
|
|
244
458
|
const tokenUsage = params?.tokenUsage;
|
|
245
459
|
if (tokenUsage && typeof tokenUsage === "object") {
|
|
246
460
|
this.eventHandler?.({
|
|
@@ -254,8 +468,15 @@ class CodexAppServerClient {
|
|
|
254
468
|
if (!item || typeof item !== "object") {
|
|
255
469
|
return method.startsWith("item/");
|
|
256
470
|
}
|
|
471
|
+
const child = this.childItemFields(params);
|
|
257
472
|
if ((method === "item/started" || method === "item/completed") && (item.type === "collabAgentToolCall" || item.type === "subAgentActivity")) {
|
|
258
|
-
this.
|
|
473
|
+
this.rememberChildLabels(item);
|
|
474
|
+
this.eventHandler?.({ type: "collab_agent_item", item, ...child });
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
if (method === "item/completed" && item.type === "userMessage") {
|
|
478
|
+
const clientId = typeof item.clientId === "string" ? item.clientId : null;
|
|
479
|
+
if (clientId && !child.subagent) this.steeredAwaitingEcho.delete(clientId);
|
|
259
480
|
return true;
|
|
260
481
|
}
|
|
261
482
|
if (method === "item/started" && item.type === "commandExecution") {
|
|
@@ -266,7 +487,8 @@ class CodexAppServerClient {
|
|
|
266
487
|
callId,
|
|
267
488
|
command: item.command,
|
|
268
489
|
cwd: item.cwd,
|
|
269
|
-
description: item.command
|
|
490
|
+
description: item.command,
|
|
491
|
+
...child
|
|
270
492
|
});
|
|
271
493
|
return true;
|
|
272
494
|
}
|
|
@@ -281,7 +503,8 @@ class CodexAppServerClient {
|
|
|
281
503
|
duration_ms: item.durationMs ?? null,
|
|
282
504
|
status: item.status,
|
|
283
505
|
cwd: item.cwd,
|
|
284
|
-
command: item.command
|
|
506
|
+
command: item.command,
|
|
507
|
+
...child
|
|
285
508
|
});
|
|
286
509
|
return true;
|
|
287
510
|
}
|
|
@@ -296,7 +519,8 @@ class CodexAppServerClient {
|
|
|
296
519
|
type: "patch_apply_begin",
|
|
297
520
|
call_id: callId,
|
|
298
521
|
callId,
|
|
299
|
-
changes: changes ?? {}
|
|
522
|
+
changes: changes ?? {},
|
|
523
|
+
...child
|
|
300
524
|
});
|
|
301
525
|
return true;
|
|
302
526
|
}
|
|
@@ -305,7 +529,8 @@ class CodexAppServerClient {
|
|
|
305
529
|
type: "patch_apply_end",
|
|
306
530
|
call_id: callId,
|
|
307
531
|
callId,
|
|
308
|
-
status: item.status
|
|
532
|
+
status: item.status,
|
|
533
|
+
...child
|
|
309
534
|
});
|
|
310
535
|
if (callId && (item.status === "completed" || item.status === "failed" || item.status === "declined")) {
|
|
311
536
|
this.rawFileChangesByItemId.delete(callId);
|
|
@@ -320,7 +545,8 @@ class CodexAppServerClient {
|
|
|
320
545
|
type: "agent_message",
|
|
321
546
|
message: text,
|
|
322
547
|
item_id: item.id,
|
|
323
|
-
phase: item.phase
|
|
548
|
+
phase: item.phase,
|
|
549
|
+
...child
|
|
324
550
|
});
|
|
325
551
|
}
|
|
326
552
|
return true;
|
|
@@ -366,7 +592,7 @@ class CodexAppServerClient {
|
|
|
366
592
|
}
|
|
367
593
|
logger.debug(`[CodexAppServer] Spawning: ${command} ${args.join(" ")}`);
|
|
368
594
|
const epoch = ++this.processEpoch;
|
|
369
|
-
const proc = spawn(command, args, {
|
|
595
|
+
const proc = spawn$1(command, args, {
|
|
370
596
|
stdio: ["pipe", "pipe", "pipe"],
|
|
371
597
|
env,
|
|
372
598
|
windowsHide: true
|
|
@@ -382,17 +608,39 @@ class CodexAppServerClient {
|
|
|
382
608
|
return;
|
|
383
609
|
}
|
|
384
610
|
this.connected = false;
|
|
611
|
+
const tail = this.stderrTail();
|
|
612
|
+
const exited = `Codex process exited (code=${code ?? "null"}${signal ? `, signal=${signal}` : ""})`;
|
|
385
613
|
for (const [id, req] of this.pending) {
|
|
386
614
|
if (req.epoch !== epoch) continue;
|
|
387
|
-
|
|
615
|
+
const error = new Error(`${exited} while waiting for ${req.method}`);
|
|
616
|
+
error.codexStderrTail = tail;
|
|
617
|
+
req.reject(error);
|
|
388
618
|
this.pending.delete(id);
|
|
389
619
|
}
|
|
620
|
+
if (this.pendingTurnCompletion) {
|
|
621
|
+
const turnId = this.pendingTurnCompletion.turnId ?? this._turnId;
|
|
622
|
+
this.dropUnechoedSteers(null, true);
|
|
623
|
+
this.eventHandler?.({
|
|
624
|
+
type: "turn_aborted",
|
|
625
|
+
reason: "process-exit",
|
|
626
|
+
...turnId ? { turn_id: turnId } : {},
|
|
627
|
+
error: { message: `${exited}${tail ? `: ${tail}` : ""}` }
|
|
628
|
+
});
|
|
629
|
+
}
|
|
390
630
|
this.resolvePendingTurn(true);
|
|
391
631
|
});
|
|
632
|
+
this.stderrRing = "";
|
|
633
|
+
this.steerUnsupported = false;
|
|
634
|
+
this.steeredAwaitingEcho.clear();
|
|
635
|
+
this.childThreadLabels.clear();
|
|
392
636
|
proc.stderr?.on("data", (chunk) => {
|
|
393
637
|
if (this.process !== proc || this.processEpoch !== epoch) return;
|
|
394
638
|
const text = chunk.toString().trim();
|
|
395
|
-
if (text)
|
|
639
|
+
if (text) {
|
|
640
|
+
logger.debug("[CodexAppServer:stderr]", contentLogMetadata(text));
|
|
641
|
+
this.stderrRing = `${this.stderrRing}
|
|
642
|
+
${text}`.slice(-400);
|
|
643
|
+
}
|
|
396
644
|
});
|
|
397
645
|
this.readline = createInterface({ input: proc.stdout });
|
|
398
646
|
this.readline.on("line", (line) => {
|
|
@@ -423,26 +671,21 @@ class CodexAppServerClient {
|
|
|
423
671
|
logger.debug(`[CodexAppServer] Disconnecting; pid=${pid ?? "none"}`);
|
|
424
672
|
this.readline?.close();
|
|
425
673
|
this.readline = null;
|
|
426
|
-
|
|
427
|
-
proc
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const killTimer = setTimeout(() => {
|
|
433
|
-
try {
|
|
434
|
-
process.kill(pid, 0);
|
|
435
|
-
process.kill(pid, "SIGKILL");
|
|
436
|
-
} catch {
|
|
437
|
-
}
|
|
438
|
-
}, 2e3);
|
|
439
|
-
killTimer.unref();
|
|
674
|
+
if (proc) {
|
|
675
|
+
stopCodexProcess(proc);
|
|
676
|
+
if (pid) {
|
|
677
|
+
const killTimer = setTimeout(() => killCodexProcessTree(proc), CodexAppServerClient.FORCE_KILL_AFTER_MS);
|
|
678
|
+
killTimer.unref();
|
|
679
|
+
}
|
|
440
680
|
}
|
|
441
681
|
this.process = null;
|
|
442
682
|
this.connected = false;
|
|
443
683
|
this._turnId = null;
|
|
444
684
|
this.notificationProtocol = "unknown";
|
|
445
685
|
this.completedTurnIds.clear();
|
|
686
|
+
this.failedTurnIds.clear();
|
|
687
|
+
this.lastTurnError = null;
|
|
688
|
+
this.clearThreadStopGrace();
|
|
446
689
|
if (!opts?.preserveThreadState) {
|
|
447
690
|
this._threadId = null;
|
|
448
691
|
this.threadDefaults = null;
|
|
@@ -452,6 +695,7 @@ class CodexAppServerClient {
|
|
|
452
695
|
req.reject(new Error(`Codex process disconnected while waiting for ${req.method}`));
|
|
453
696
|
this.pending.delete(id);
|
|
454
697
|
}
|
|
698
|
+
this.dropUnechoedSteers(null, true);
|
|
455
699
|
this.resolvePendingTurn(true);
|
|
456
700
|
if (this.sandboxCleanup) {
|
|
457
701
|
try {
|
|
@@ -655,20 +899,137 @@ class CodexAppServerClient {
|
|
|
655
899
|
// ─── Turn management ────────────────────────────────────────
|
|
656
900
|
/** Default grace period after interrupt before forcing a restart (ms). */
|
|
657
901
|
static ABORT_GRACE_MS = 3e3;
|
|
902
|
+
/** How long the graceful stop gets before the process tree is killed (disconnectInternal). */
|
|
903
|
+
static FORCE_KILL_AFTER_MS = 2e3;
|
|
658
904
|
hasPendingTurnCompletion() {
|
|
659
905
|
return this.pendingTurnCompletion !== null;
|
|
660
906
|
}
|
|
661
907
|
resolvePendingTurn(aborted) {
|
|
662
908
|
if (!this.pendingTurnCompletion) return;
|
|
663
909
|
this.clearTurnWatchdog();
|
|
910
|
+
this.clearThreadStopGrace();
|
|
664
911
|
this.pendingTurnCompletion.resolve(aborted);
|
|
665
912
|
this.pendingTurnCompletion = null;
|
|
913
|
+
this.releaseTurnOpenWaiters();
|
|
666
914
|
}
|
|
667
915
|
markPendingTurnStarted(turnId) {
|
|
668
916
|
if (!this.pendingTurnCompletion) return;
|
|
917
|
+
this.pendingTurnCompletion.opened = true;
|
|
669
918
|
if (turnId) {
|
|
670
919
|
this.pendingTurnCompletion.turnId = turnId;
|
|
671
920
|
}
|
|
921
|
+
this.releaseTurnOpenWaiters();
|
|
922
|
+
}
|
|
923
|
+
// ─── B-628: steering the running turn ───────────────────────
|
|
924
|
+
/** How long a steer waits for the pending turn to open before the message is queued instead. */
|
|
925
|
+
static STEER_OPEN_WAIT_MS = 5e3;
|
|
926
|
+
/** A turn Codex has reported running, under an id `turn/steer` can name. */
|
|
927
|
+
turnIsOpen() {
|
|
928
|
+
return this.pendingTurnCompletion?.opened === true && this._turnId !== null;
|
|
929
|
+
}
|
|
930
|
+
releaseTurnOpenWaiters() {
|
|
931
|
+
const waiters = this.turnOpenWaiters;
|
|
932
|
+
this.turnOpenWaiters = [];
|
|
933
|
+
for (const release of waiters) release();
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Waits for the pending turn to open — `turn/start` is in flight, or the runner is still
|
|
937
|
+
* bringing a released app-server back before sending it (B-461) — as Orca's send does
|
|
938
|
+
* (codex-structured-turn-open-wait.ts, 5 s). Resolves early when the turn ends instead.
|
|
939
|
+
*/
|
|
940
|
+
async waitForTurnOpen(timeoutMs) {
|
|
941
|
+
if (this.turnIsOpen()) return true;
|
|
942
|
+
if (timeoutMs <= 0) return false;
|
|
943
|
+
await new Promise((resolve) => {
|
|
944
|
+
const release = () => {
|
|
945
|
+
clearTimeout(timer);
|
|
946
|
+
resolve();
|
|
947
|
+
};
|
|
948
|
+
const timer = setTimeout(release, timeoutMs);
|
|
949
|
+
timer.unref();
|
|
950
|
+
this.turnOpenWaiters.push(release);
|
|
951
|
+
});
|
|
952
|
+
return this.turnIsOpen();
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* B-628: send a message into the turn running right now (`turn/steer`, codex ≥ 0.148 /
|
|
956
|
+
* app-server-protocol v2 `TurnSteerParams`). Codex appends it to that turn's pending input
|
|
957
|
+
* and the model reads it before its next call; the turn keeps its own model, approval
|
|
958
|
+
* policy and sandbox — the runner applies any change on the next `turn/start`. Codex
|
|
959
|
+
* refuses before taking any input (expectedTurnId is a precondition), so a non-`steered`
|
|
960
|
+
* outcome always means "queue it for the next turn as before": `no-turn` (nothing running,
|
|
961
|
+
* or it ended while we waited), `unsupported` (this app-server has no `turn/steer`,
|
|
962
|
+
* remembered until it is respawned), `refused` (Codex declined this one), `failed`
|
|
963
|
+
* (transport — the turn itself is reported through its own events).
|
|
964
|
+
*/
|
|
965
|
+
async steerTurn(input, opts) {
|
|
966
|
+
if (this.steerUnsupported) return "unsupported";
|
|
967
|
+
if (this.pendingInterrupt) return "no-turn";
|
|
968
|
+
const opened = await this.waitForTurnOpen(opts?.waitForTurnMs ?? CodexAppServerClient.STEER_OPEN_WAIT_MS);
|
|
969
|
+
const threadId = this._threadId;
|
|
970
|
+
const turnId = this._turnId;
|
|
971
|
+
if (!opened || !threadId || !turnId || !this.connected || this.pendingInterrupt) return "no-turn";
|
|
972
|
+
const params = {
|
|
973
|
+
threadId,
|
|
974
|
+
expectedTurnId: turnId,
|
|
975
|
+
...input.clientId ? { clientUserMessageId: input.clientId } : {},
|
|
976
|
+
input: [
|
|
977
|
+
{ type: "text", text: input.text },
|
|
978
|
+
...(input.images ?? []).map((path) => ({ type: "localImage", path }))
|
|
979
|
+
]
|
|
980
|
+
};
|
|
981
|
+
try {
|
|
982
|
+
const result = await this.request("turn/steer", params);
|
|
983
|
+
const steeredTurnId = typeof result?.turnId === "string" && result.turnId.length > 0 ? result.turnId : turnId;
|
|
984
|
+
if (input.clientId) this.steeredAwaitingEcho.set(input.clientId, steeredTurnId);
|
|
985
|
+
logger.debug(`[CodexAppServer] Steered a message into turn ${steeredTurnId}`);
|
|
986
|
+
return "steered";
|
|
987
|
+
} catch (error) {
|
|
988
|
+
const code = jsonRpcErrorCodeOf(error);
|
|
989
|
+
if (code === -32601) {
|
|
990
|
+
this.steerUnsupported = true;
|
|
991
|
+
logger.debug("[CodexAppServer] turn/steer is not supported by this codex; queuing instead");
|
|
992
|
+
return "unsupported";
|
|
993
|
+
}
|
|
994
|
+
if (code !== null) {
|
|
995
|
+
logger.debug("[CodexAppServer] turn/steer refused:", errorLogMetadata(error));
|
|
996
|
+
return "refused";
|
|
997
|
+
}
|
|
998
|
+
logger.debug("[CodexAppServer] turn/steer failed:", errorLogMetadata(error));
|
|
999
|
+
return "failed";
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
/** Default wait for Codex's `turn/completed` after its thread reported idle/systemError. */
|
|
1003
|
+
static THREAD_STOP_GRACE_MS = 1500;
|
|
1004
|
+
/**
|
|
1005
|
+
* B-625: the own thread stopped (idle / systemError) while a turn was pending and
|
|
1006
|
+
* had opened. Codex's `turn/completed` follows at once in every probed release
|
|
1007
|
+
* (0.154.0: 1 ms); this is the net under a release that does not send it, so the
|
|
1008
|
+
* session never waits on the silence watchdog for a turn Codex has already left.
|
|
1009
|
+
*/
|
|
1010
|
+
armThreadStopGrace(statusType) {
|
|
1011
|
+
this.clearThreadStopGrace();
|
|
1012
|
+
const graceMs = this.pendingTurnCompletion?.threadStopGraceMs ?? CodexAppServerClient.THREAD_STOP_GRACE_MS;
|
|
1013
|
+
this.threadStopGrace = setTimeout(() => {
|
|
1014
|
+
this.threadStopGrace = null;
|
|
1015
|
+
const pending = this.pendingTurnCompletion;
|
|
1016
|
+
if (!pending) return;
|
|
1017
|
+
const turnId = pending.turnId ?? this._turnId;
|
|
1018
|
+
const lastError = this.lastTurnError && (!turnId || !this.lastTurnError.turnId || this.lastTurnError.turnId === turnId) ? this.lastTurnError.error : null;
|
|
1019
|
+
logger.warn(`[CodexAppServer] Thread went ${statusType} and no turn/completed followed for turn ${turnId ?? "?"}; ending the turn`);
|
|
1020
|
+
const failed = statusType === "systemError" || lastError !== null;
|
|
1021
|
+
this.emitRawTurnCompletion(
|
|
1022
|
+
turnId,
|
|
1023
|
+
failed ? "failed" : "completed",
|
|
1024
|
+
lastError ?? (failed ? { message: "Codex reported a system error and ended the turn without a verdict." } : null),
|
|
1025
|
+
`thread/status/changed:${statusType}`
|
|
1026
|
+
);
|
|
1027
|
+
}, graceMs);
|
|
1028
|
+
}
|
|
1029
|
+
clearThreadStopGrace() {
|
|
1030
|
+
if (!this.threadStopGrace) return;
|
|
1031
|
+
clearTimeout(this.threadStopGrace);
|
|
1032
|
+
this.threadStopGrace = null;
|
|
672
1033
|
}
|
|
673
1034
|
tryResolvePendingTurn(aborted, turnId, source) {
|
|
674
1035
|
const pending = this.pendingTurnCompletion;
|
|
@@ -771,6 +1132,8 @@ class CodexAppServerClient {
|
|
|
771
1132
|
}
|
|
772
1133
|
/** Default timeout for waiting on turn completion (ms). 10 minutes. */
|
|
773
1134
|
static TURN_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
1135
|
+
/** How much of the app-server's stderr is kept for its exit message. */
|
|
1136
|
+
static STDERR_TAIL_CHARS = 400;
|
|
774
1137
|
/**
|
|
775
1138
|
* Send a user turn and wait for it to complete (task_complete or turn_aborted).
|
|
776
1139
|
* Returns { aborted: true } if the turn was aborted (user cancel, permission reject, etc.).
|
|
@@ -785,8 +1148,11 @@ class CodexAppServerClient {
|
|
|
785
1148
|
const completion = new Promise((resolve) => {
|
|
786
1149
|
this.pendingTurnCompletion = {
|
|
787
1150
|
resolve,
|
|
788
|
-
turnId: null
|
|
1151
|
+
turnId: null,
|
|
1152
|
+
opened: false,
|
|
1153
|
+
threadStopGraceMs: opts?.threadStopGraceMs ?? CodexAppServerClient.THREAD_STOP_GRACE_MS
|
|
789
1154
|
};
|
|
1155
|
+
this.lastTurnError = null;
|
|
790
1156
|
this.armTurnWatchdog(timeoutMs);
|
|
791
1157
|
});
|
|
792
1158
|
try {
|
|
@@ -794,6 +1160,7 @@ class CodexAppServerClient {
|
|
|
794
1160
|
} catch (err) {
|
|
795
1161
|
this.clearTurnWatchdog();
|
|
796
1162
|
this.pendingTurnCompletion = null;
|
|
1163
|
+
this.releaseTurnOpenWaiters();
|
|
797
1164
|
throw err;
|
|
798
1165
|
}
|
|
799
1166
|
const aborted = await completion;
|
|
@@ -811,6 +1178,7 @@ class CodexAppServerClient {
|
|
|
811
1178
|
}
|
|
812
1179
|
logger.warn(`[CodexAppServer] No notification from Codex for ${timeoutMs}ms \u2014 giving the turn up as aborted`);
|
|
813
1180
|
const turnId = this.pendingTurnCompletion.turnId ?? this._turnId;
|
|
1181
|
+
this.dropUnechoedSteers(turnId, true);
|
|
814
1182
|
this.eventHandler?.({
|
|
815
1183
|
type: "turn_aborted",
|
|
816
1184
|
reason: "timeout",
|
|
@@ -861,12 +1229,16 @@ class CodexAppServerClient {
|
|
|
861
1229
|
logger.debug(
|
|
862
1230
|
`[CodexAppServer] Clearing thread state: thread=${this._threadId ?? "none"} turn=${this._turnId ?? "none"}`
|
|
863
1231
|
);
|
|
1232
|
+
this.dropUnechoedSteers(null, true);
|
|
864
1233
|
this.resolvePendingTurn(true);
|
|
865
1234
|
this._threadId = null;
|
|
866
1235
|
this._turnId = null;
|
|
867
1236
|
this.threadDefaults = null;
|
|
868
1237
|
this.completedTurnIds.clear();
|
|
1238
|
+
this.failedTurnIds.clear();
|
|
1239
|
+
this.lastTurnError = null;
|
|
869
1240
|
this.rawFileChangesByItemId.clear();
|
|
1241
|
+
this.childThreadLabels.clear();
|
|
870
1242
|
}
|
|
871
1243
|
// ─── JSON-RPC transport ─────────────────────────────────────
|
|
872
1244
|
/** Default timeout for RPC requests (ms). */
|
|
@@ -947,7 +1319,9 @@ class CodexAppServerClient {
|
|
|
947
1319
|
}
|
|
948
1320
|
this.pending.delete(msg.id);
|
|
949
1321
|
if (msg.error) {
|
|
950
|
-
|
|
1322
|
+
const error = new Error(`${pending.method}: ${msg.error.message} (code=${msg.error.code})`);
|
|
1323
|
+
if (typeof msg.error.code === "number") error.jsonRpcCode = msg.error.code;
|
|
1324
|
+
pending.reject(error);
|
|
951
1325
|
} else {
|
|
952
1326
|
pending.resolve(msg.result);
|
|
953
1327
|
}
|
|
@@ -1134,6 +1508,7 @@ class CodexAppServerClient {
|
|
|
1134
1508
|
}
|
|
1135
1509
|
if (method === "thread/started" || method === "turn/started" || method === "turn/completed" || method === "thread/status/changed") {
|
|
1136
1510
|
logger.debug(`[CodexAppServer] Lifecycle notification: ${method}`);
|
|
1511
|
+
if (!this.isOwnThreadFrame(params)) return;
|
|
1137
1512
|
if (method === "turn/started") {
|
|
1138
1513
|
const turnId = this.extractTurnId(params);
|
|
1139
1514
|
if (turnId) {
|
|
@@ -1160,19 +1535,28 @@ class CodexAppServerClient {
|
|
|
1160
1535
|
logger.debug(`[CodexAppServer] mcpServer startup status:`, params);
|
|
1161
1536
|
return;
|
|
1162
1537
|
}
|
|
1163
|
-
if (method === "error"
|
|
1538
|
+
if (method === "error") {
|
|
1539
|
+
if (!this.isOwnThreadFrame(params)) return;
|
|
1164
1540
|
const error = params?.error ?? {};
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1541
|
+
const turnId = typeof params?.turnId === "string" && params.turnId.length > 0 ? params.turnId : null;
|
|
1542
|
+
if (params?.willRetry === true) {
|
|
1543
|
+
const message = typeof error.message === "string" ? error.message : "";
|
|
1544
|
+
const info = error.codexErrorInfo;
|
|
1545
|
+
const httpStatus = info && typeof info === "object" ? info.responseStreamDisconnected?.httpStatusCode ?? info.httpStatusCode ?? null : null;
|
|
1546
|
+
this.eventHandler?.({
|
|
1547
|
+
type: "turn_retry",
|
|
1548
|
+
message,
|
|
1549
|
+
...typeof httpStatus === "number" ? { http_status: httpStatus } : {},
|
|
1550
|
+
...turnId ? { turn_id: turnId } : {},
|
|
1551
|
+
...typeof error.additionalDetails === "string" ? { details: error.additionalDetails } : {}
|
|
1552
|
+
});
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
this.lastTurnError = { turnId, error };
|
|
1556
|
+
const pending = this.pendingTurnCompletion;
|
|
1557
|
+
if (pending && !pending.opened && (turnId === null || pending.turnId === null || pending.turnId === turnId)) {
|
|
1558
|
+
this.emitRawTurnCompletion(turnId ?? pending.turnId, "failed", error, "error:unopened-turn");
|
|
1559
|
+
}
|
|
1176
1560
|
return;
|
|
1177
1561
|
}
|
|
1178
1562
|
logger.debug(`[CodexAppServer] Notification: ${method}`);
|