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
|
@@ -3,12 +3,100 @@
|
|
|
3
3
|
var node_child_process = require('node:child_process');
|
|
4
4
|
var spawn = require('cross-spawn');
|
|
5
5
|
var node_readline = require('node:readline');
|
|
6
|
-
var logger = require('./logger-
|
|
6
|
+
var logger = require('./logger-zoO54Zsy.cjs');
|
|
7
7
|
var contentLogMetadata = require('./contentLogMetadata-ZSFk_NjQ.cjs');
|
|
8
8
|
var manager = require('./manager-HrEj442o.cjs');
|
|
9
|
-
var _package = require('./package-
|
|
10
|
-
var rateLimitWindows = require('./rateLimitWindows-
|
|
9
|
+
var _package = require('./package-A7zOGx8B.cjs');
|
|
10
|
+
var rateLimitWindows = require('./rateLimitWindows-82izLQy8.cjs');
|
|
11
11
|
|
|
12
|
+
const CODEX_HELPER_LABEL_MAX_CHARS = 80;
|
|
13
|
+
function codexHelperLabel(prompt) {
|
|
14
|
+
if (typeof prompt !== "string") return null;
|
|
15
|
+
const collapsed = prompt.replace(/\s+/g, " ").trim();
|
|
16
|
+
if (collapsed.length === 0) return null;
|
|
17
|
+
if (collapsed.length <= CODEX_HELPER_LABEL_MAX_CHARS) return collapsed;
|
|
18
|
+
const keep = CODEX_HELPER_LABEL_MAX_CHARS - 1;
|
|
19
|
+
const last = collapsed.charCodeAt(keep - 1);
|
|
20
|
+
const end = last >= 55296 && last <= 56319 ? keep - 1 : keep;
|
|
21
|
+
return `${collapsed.slice(0, end)}\u2026`;
|
|
22
|
+
}
|
|
23
|
+
function codexAgentPathSegments(agentPath) {
|
|
24
|
+
if (typeof agentPath !== "string") return [];
|
|
25
|
+
return agentPath.split("/").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
|
|
26
|
+
}
|
|
27
|
+
function isRootAgentPath(segments) {
|
|
28
|
+
return segments.length === 1 && segments[0] === "root";
|
|
29
|
+
}
|
|
30
|
+
function codexChildAnnouncements(item) {
|
|
31
|
+
if (item.type === "subAgentActivity") {
|
|
32
|
+
const threadId = item.agentThreadId;
|
|
33
|
+
if (typeof threadId !== "string" || threadId.length === 0) return [];
|
|
34
|
+
const segments = codexAgentPathSegments(item.agentPath);
|
|
35
|
+
if (isRootAgentPath(segments)) return [];
|
|
36
|
+
const leaf = segments.at(-1);
|
|
37
|
+
return [{ threadId, label: leaf && leaf.length > 0 ? leaf : null }];
|
|
38
|
+
}
|
|
39
|
+
if (item.type === "collabAgentToolCall") {
|
|
40
|
+
const receivers = Array.isArray(item.receiverThreadIds) ? item.receiverThreadIds.filter((id) => typeof id === "string" && id.length > 0) : [];
|
|
41
|
+
const label = item.tool === "spawnAgent" ? codexHelperLabel(item.prompt) : null;
|
|
42
|
+
return [...new Set(receivers)].map((threadId) => ({ threadId, label }));
|
|
43
|
+
}
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function stopCodexProcess(proc, deps = {}) {
|
|
48
|
+
const platform = deps.platform ?? process.platform;
|
|
49
|
+
try {
|
|
50
|
+
proc.stdin?.end();
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
if (platform === "win32") return;
|
|
54
|
+
try {
|
|
55
|
+
proc.kill("SIGTERM");
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function killCodexProcessTree(proc, deps = {}) {
|
|
60
|
+
if (proc.exitCode != null || proc.signalCode != null) return;
|
|
61
|
+
const killRoot = () => {
|
|
62
|
+
try {
|
|
63
|
+
proc.kill("SIGKILL");
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const pid = proc.pid;
|
|
68
|
+
if (!pid || !("exitCode" in proc)) {
|
|
69
|
+
killRoot();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const platform = deps.platform ?? process.platform;
|
|
73
|
+
const spawn = deps.spawn ?? ((command, args, options) => node_child_process.spawn(command, args, options));
|
|
74
|
+
if (platform === "win32") {
|
|
75
|
+
try {
|
|
76
|
+
const killer = spawn("taskkill", ["/pid", String(pid), "/t", "/f"], { stdio: "ignore", windowsHide: true });
|
|
77
|
+
killer.on("error", killRoot);
|
|
78
|
+
killer.on("exit", (code) => {
|
|
79
|
+
if (code !== 0) killRoot();
|
|
80
|
+
});
|
|
81
|
+
killer.unref();
|
|
82
|
+
} catch {
|
|
83
|
+
killRoot();
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const reaper = spawn("pkill", ["-KILL", "-P", String(pid)], { stdio: "ignore", windowsHide: true });
|
|
89
|
+
reaper.on("error", () => void 0);
|
|
90
|
+
reaper.unref();
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
killRoot();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function jsonRpcErrorCodeOf(error) {
|
|
97
|
+
const code = error?.jsonRpcCode;
|
|
98
|
+
return typeof code === "number" ? code : null;
|
|
99
|
+
}
|
|
12
100
|
function isAppServerAvailable() {
|
|
13
101
|
try {
|
|
14
102
|
const version = node_child_process.execSync("codex --version", { encoding: "utf8", windowsHide: true }).trim();
|
|
@@ -91,7 +179,6 @@ class CodexAppServerClient {
|
|
|
91
179
|
_turnId = null;
|
|
92
180
|
threadDefaults = null;
|
|
93
181
|
// Turn completion tracking for the currently active sendTurnAndWait call.
|
|
94
|
-
// A completion event only resolves once we have seen task_started for this turn.
|
|
95
182
|
pendingTurnCompletion = null;
|
|
96
183
|
// Tracks in-flight interruptTurn() RPCs so sendTurnAndWait can wait for them
|
|
97
184
|
// before starting a new turn (prevents stale turn/interrupt from aborting the next turn).
|
|
@@ -101,6 +188,22 @@ class CodexAppServerClient {
|
|
|
101
188
|
idleRelease = null;
|
|
102
189
|
notificationProtocol = "unknown";
|
|
103
190
|
completedTurnIds = /* @__PURE__ */ new Set();
|
|
191
|
+
/** B-625: turns whose failure has already been forwarded, so a second failed frame is not. */
|
|
192
|
+
failedTurnIds = /* @__PURE__ */ new Set();
|
|
193
|
+
// B-625: the app-server speaks for every thread it hosts — the session's own and the
|
|
194
|
+
// children `spawn_agent` opens — so a lifecycle frame (turn/started, turn/completed,
|
|
195
|
+
// thread/status/changed, error, thread/closed) is the session's only when its `threadId`
|
|
196
|
+
// is ours; a frame naming no thread belongs to the thread it was asked on (Orca's rule,
|
|
197
|
+
// src/main/codex/codex-structured-turn-open-wait.ts). Before this, a child's
|
|
198
|
+
// `turn/completed` ended the parent's turn in the UI and a child's `turn/started`
|
|
199
|
+
// retargeted `turn/interrupt` at the child.
|
|
200
|
+
/** The own thread's last turn-ending `error` (willRetry=false), for a turn end that arrives without a verdict. */
|
|
201
|
+
lastTurnError = null;
|
|
202
|
+
/** Own thread reported idle/systemError while a turn was pending: Codex's `turn/completed`
|
|
203
|
+
* follows within a millisecond; this ends the turn if it does not (see armThreadStopGrace). */
|
|
204
|
+
threadStopGrace = null;
|
|
205
|
+
/** Last few hundred chars of the app-server's stderr: Codex's own last words when it dies. */
|
|
206
|
+
stderrRing = "";
|
|
104
207
|
rawFileChangesByItemId = /* @__PURE__ */ new Map();
|
|
105
208
|
// B-622: inactivity watchdog for the pending turn — re-armed by every
|
|
106
209
|
// notification, never a cap on how long a turn may run (see sendTurnAndWait).
|
|
@@ -108,6 +211,18 @@ class CodexAppServerClient {
|
|
|
108
211
|
// Approval / elicitation requests we are still answering: a human may take
|
|
109
212
|
// longer than the watchdog to decide, and that is not Codex going silent.
|
|
110
213
|
inflightServerRequests = 0;
|
|
214
|
+
// B-628: `turn/steer` — a message sent into the running turn (approach from Orca,
|
|
215
|
+
// src/main/codex/codex-structured-turn-start.ts). This app-server has no such
|
|
216
|
+
// method (-32601) until it is respawned; a refusal is per call (the turn it named
|
|
217
|
+
// ended or changed meanwhile, a review/compact turn).
|
|
218
|
+
steerUnsupported = false;
|
|
219
|
+
/** Steered inputs Codex has not echoed back as a `userMessage` item yet: clientId → turnId. */
|
|
220
|
+
steeredAwaitingEcho = /* @__PURE__ */ new Map();
|
|
221
|
+
/** Waiters for the pending turn to open (steerTurn); released when it opens or ends. */
|
|
222
|
+
turnOpenWaiters = [];
|
|
223
|
+
/** B-628: the names Codex gave the child threads it spawned (see codexChildThreads.ts). */
|
|
224
|
+
childThreadLabels = /* @__PURE__ */ new Map();
|
|
225
|
+
static MAX_CHILD_LABELS = 128;
|
|
111
226
|
// Handlers set by the consumer (runCodex.ts)
|
|
112
227
|
eventHandler = null;
|
|
113
228
|
approvalHandler = null;
|
|
@@ -169,8 +284,73 @@ class CodexAppServerClient {
|
|
|
169
284
|
const status = params?.turn?.status ?? params?.status ?? null;
|
|
170
285
|
return typeof status === "string" && status.length > 0 ? status : null;
|
|
171
286
|
}
|
|
287
|
+
extractThreadId(params) {
|
|
288
|
+
const threadId = params?.threadId ?? params?.thread_id ?? params?.thread?.id ?? null;
|
|
289
|
+
return typeof threadId === "string" && threadId.length > 0 ? threadId : null;
|
|
290
|
+
}
|
|
291
|
+
/** B-625: whether a lifecycle frame is about the session's own thread (see the field comment). */
|
|
292
|
+
isOwnThreadFrame(params) {
|
|
293
|
+
const threadId = this.extractThreadId(params);
|
|
294
|
+
return threadId === null || this._threadId === null || threadId === this._threadId;
|
|
295
|
+
}
|
|
296
|
+
/** B-628: the child thread a frame belongs to, or null for the session's own. */
|
|
297
|
+
childThreadIdOf(params) {
|
|
298
|
+
return this.isOwnThreadFrame(params) ? null : this.extractThreadId(params);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* B-628: what a child thread's item carries so the mapper files it under the child's
|
|
302
|
+
* card (`subagent` → the session protocol's sub-agent envelope, as a Claude Task's
|
|
303
|
+
* children are) instead of in the main conversation as the session's own work.
|
|
304
|
+
*/
|
|
305
|
+
childItemFields(params) {
|
|
306
|
+
const child = this.childThreadIdOf(params);
|
|
307
|
+
if (!child) return {};
|
|
308
|
+
const label = this.childThreadLabels.get(child);
|
|
309
|
+
return { subagent: child, ...label ? { subagent_label: label } : {} };
|
|
310
|
+
}
|
|
311
|
+
rememberChildLabels(item) {
|
|
312
|
+
for (const { threadId, label } of codexChildAnnouncements(item)) {
|
|
313
|
+
if (!label || this.childThreadLabels.has(threadId)) continue;
|
|
314
|
+
if (this.childThreadLabels.size >= CodexAppServerClient.MAX_CHILD_LABELS) {
|
|
315
|
+
const oldest = this.childThreadLabels.keys().next().value;
|
|
316
|
+
if (oldest !== void 0) this.childThreadLabels.delete(oldest);
|
|
317
|
+
}
|
|
318
|
+
this.childThreadLabels.set(threadId, label);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* B-628: steered inputs the ended turn never echoed. `report` = the turn did not complete
|
|
323
|
+
* (interrupted, failed, app-server gone, given up): Codex never read them, tell the runner.
|
|
324
|
+
* A turn that *completed* drains its pending input before it ends (core/src/session/turn.rs),
|
|
325
|
+
* so an unechoed input there is a codex that accepted the steer but echoes no `clientId` —
|
|
326
|
+
* forgotten silently, because a false「not delivered」on a message the model did read would
|
|
327
|
+
* be worse than today's silence.
|
|
328
|
+
*/
|
|
329
|
+
dropUnechoedSteers(turnId, report) {
|
|
330
|
+
const dropped = [];
|
|
331
|
+
for (const [clientId, steeredTurn] of this.steeredAwaitingEcho) {
|
|
332
|
+
if (turnId === null || steeredTurn === turnId) {
|
|
333
|
+
dropped.push(clientId);
|
|
334
|
+
this.steeredAwaitingEcho.delete(clientId);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (dropped.length === 0) return;
|
|
338
|
+
if (!report) {
|
|
339
|
+
logger.logger.debug(`[CodexAppServer] ${dropped.length} steered message(s) completed without an echo; not reported`);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
this.eventHandler?.({
|
|
343
|
+
type: "steer_dropped",
|
|
344
|
+
client_ids: dropped,
|
|
345
|
+
...turnId ? { turn_id: turnId } : {}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
/** Codex's last stderr lines, one line, for a message a person reads. */
|
|
349
|
+
stderrTail() {
|
|
350
|
+
return this.stderrRing.replace(/\x1b\[[0-9;]*m/g, "").replace(/\s+/g, " ").trim();
|
|
351
|
+
}
|
|
172
352
|
shouldHandleRawNotification(method) {
|
|
173
|
-
const isRawNotification = method === "thread/started" || method === "turn/started" || method === "turn/completed" || method === "thread/status/changed" || method === "thread/tokenUsage/updated" || method.startsWith("item/");
|
|
353
|
+
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/");
|
|
174
354
|
if (!isRawNotification) {
|
|
175
355
|
return false;
|
|
176
356
|
}
|
|
@@ -185,15 +365,21 @@ class CodexAppServerClient {
|
|
|
185
365
|
emitRawTurnCompletion(turnId, status, error, source) {
|
|
186
366
|
const aborted = status === "cancelled" || status === "canceled" || status === "aborted" || status === "interrupted";
|
|
187
367
|
const failed = status === "failed" || error !== void 0 && error !== null;
|
|
368
|
+
this.clearThreadStopGrace();
|
|
188
369
|
this.tryResolvePendingTurn(aborted, turnId, source);
|
|
189
370
|
this._turnId = null;
|
|
190
371
|
this.maybeRefreshRateLimits();
|
|
372
|
+
if (this.lastTurnError && (!turnId || !this.lastTurnError.turnId || this.lastTurnError.turnId === turnId)) {
|
|
373
|
+
this.lastTurnError = null;
|
|
374
|
+
}
|
|
191
375
|
if (turnId && this.completedTurnIds.has(turnId)) {
|
|
192
|
-
if (!failed) return;
|
|
376
|
+
if (!failed || this.failedTurnIds.has(turnId)) return;
|
|
193
377
|
}
|
|
194
378
|
if (turnId) {
|
|
195
379
|
this.completedTurnIds.add(turnId);
|
|
380
|
+
if (failed) this.failedTurnIds.add(turnId);
|
|
196
381
|
}
|
|
382
|
+
this.dropUnechoedSteers(turnId, aborted || failed);
|
|
197
383
|
if (aborted) {
|
|
198
384
|
this.eventHandler?.({
|
|
199
385
|
type: "turn_aborted",
|
|
@@ -215,6 +401,10 @@ class CodexAppServerClient {
|
|
|
215
401
|
return false;
|
|
216
402
|
}
|
|
217
403
|
if (method === "turn/started") {
|
|
404
|
+
if (!this.isOwnThreadFrame(params)) {
|
|
405
|
+
logger.logger.debug(`[CodexAppServer] Child thread ${this.extractThreadId(params)} started a turn`);
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
218
408
|
const turnId = this.extractTurnId(params);
|
|
219
409
|
if (turnId) {
|
|
220
410
|
this._turnId = turnId;
|
|
@@ -227,6 +417,15 @@ class CodexAppServerClient {
|
|
|
227
417
|
return true;
|
|
228
418
|
}
|
|
229
419
|
if (method === "turn/completed") {
|
|
420
|
+
if (!this.isOwnThreadFrame(params)) {
|
|
421
|
+
logger.logger.debug(`[CodexAppServer] Child thread ${this.extractThreadId(params)} ended a turn`);
|
|
422
|
+
this.eventHandler?.({
|
|
423
|
+
type: "subagent_turn_end",
|
|
424
|
+
...this.childItemFields(params),
|
|
425
|
+
status: this.extractTurnStatus(params)
|
|
426
|
+
});
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
230
429
|
this.emitRawTurnCompletion(
|
|
231
430
|
this.extractTurnId(params),
|
|
232
431
|
this.extractTurnStatus(params),
|
|
@@ -236,13 +435,28 @@ class CodexAppServerClient {
|
|
|
236
435
|
return true;
|
|
237
436
|
}
|
|
238
437
|
if (method === "thread/status/changed") {
|
|
438
|
+
if (!this.isOwnThreadFrame(params)) return true;
|
|
239
439
|
const statusType = params?.status?.type;
|
|
240
|
-
if (statusType === "idle" && this.pendingTurnCompletion) {
|
|
241
|
-
this.
|
|
440
|
+
if ((statusType === "idle" || statusType === "systemError") && this.pendingTurnCompletion?.opened) {
|
|
441
|
+
this.armThreadStopGrace(statusType);
|
|
442
|
+
} else if (statusType === "active") {
|
|
443
|
+
this.clearThreadStopGrace();
|
|
444
|
+
}
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
if (method === "thread/closed") {
|
|
448
|
+
if (this.isOwnThreadFrame(params) && this.pendingTurnCompletion) {
|
|
449
|
+
this.emitRawTurnCompletion(
|
|
450
|
+
this.pendingTurnCompletion.turnId ?? this._turnId,
|
|
451
|
+
"aborted",
|
|
452
|
+
{ message: "Codex closed the thread before the turn ended." },
|
|
453
|
+
method
|
|
454
|
+
);
|
|
242
455
|
}
|
|
243
456
|
return true;
|
|
244
457
|
}
|
|
245
458
|
if (method === "thread/tokenUsage/updated") {
|
|
459
|
+
if (!this.isOwnThreadFrame(params)) return true;
|
|
246
460
|
const tokenUsage = params?.tokenUsage;
|
|
247
461
|
if (tokenUsage && typeof tokenUsage === "object") {
|
|
248
462
|
this.eventHandler?.({
|
|
@@ -256,8 +470,15 @@ class CodexAppServerClient {
|
|
|
256
470
|
if (!item || typeof item !== "object") {
|
|
257
471
|
return method.startsWith("item/");
|
|
258
472
|
}
|
|
473
|
+
const child = this.childItemFields(params);
|
|
259
474
|
if ((method === "item/started" || method === "item/completed") && (item.type === "collabAgentToolCall" || item.type === "subAgentActivity")) {
|
|
260
|
-
this.
|
|
475
|
+
this.rememberChildLabels(item);
|
|
476
|
+
this.eventHandler?.({ type: "collab_agent_item", item, ...child });
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
if (method === "item/completed" && item.type === "userMessage") {
|
|
480
|
+
const clientId = typeof item.clientId === "string" ? item.clientId : null;
|
|
481
|
+
if (clientId && !child.subagent) this.steeredAwaitingEcho.delete(clientId);
|
|
261
482
|
return true;
|
|
262
483
|
}
|
|
263
484
|
if (method === "item/started" && item.type === "commandExecution") {
|
|
@@ -268,7 +489,8 @@ class CodexAppServerClient {
|
|
|
268
489
|
callId,
|
|
269
490
|
command: item.command,
|
|
270
491
|
cwd: item.cwd,
|
|
271
|
-
description: item.command
|
|
492
|
+
description: item.command,
|
|
493
|
+
...child
|
|
272
494
|
});
|
|
273
495
|
return true;
|
|
274
496
|
}
|
|
@@ -283,7 +505,8 @@ class CodexAppServerClient {
|
|
|
283
505
|
duration_ms: item.durationMs ?? null,
|
|
284
506
|
status: item.status,
|
|
285
507
|
cwd: item.cwd,
|
|
286
|
-
command: item.command
|
|
508
|
+
command: item.command,
|
|
509
|
+
...child
|
|
287
510
|
});
|
|
288
511
|
return true;
|
|
289
512
|
}
|
|
@@ -298,7 +521,8 @@ class CodexAppServerClient {
|
|
|
298
521
|
type: "patch_apply_begin",
|
|
299
522
|
call_id: callId,
|
|
300
523
|
callId,
|
|
301
|
-
changes: changes ?? {}
|
|
524
|
+
changes: changes ?? {},
|
|
525
|
+
...child
|
|
302
526
|
});
|
|
303
527
|
return true;
|
|
304
528
|
}
|
|
@@ -307,7 +531,8 @@ class CodexAppServerClient {
|
|
|
307
531
|
type: "patch_apply_end",
|
|
308
532
|
call_id: callId,
|
|
309
533
|
callId,
|
|
310
|
-
status: item.status
|
|
534
|
+
status: item.status,
|
|
535
|
+
...child
|
|
311
536
|
});
|
|
312
537
|
if (callId && (item.status === "completed" || item.status === "failed" || item.status === "declined")) {
|
|
313
538
|
this.rawFileChangesByItemId.delete(callId);
|
|
@@ -322,7 +547,8 @@ class CodexAppServerClient {
|
|
|
322
547
|
type: "agent_message",
|
|
323
548
|
message: text,
|
|
324
549
|
item_id: item.id,
|
|
325
|
-
phase: item.phase
|
|
550
|
+
phase: item.phase,
|
|
551
|
+
...child
|
|
326
552
|
});
|
|
327
553
|
}
|
|
328
554
|
return true;
|
|
@@ -384,17 +610,39 @@ class CodexAppServerClient {
|
|
|
384
610
|
return;
|
|
385
611
|
}
|
|
386
612
|
this.connected = false;
|
|
613
|
+
const tail = this.stderrTail();
|
|
614
|
+
const exited = `Codex process exited (code=${code ?? "null"}${signal ? `, signal=${signal}` : ""})`;
|
|
387
615
|
for (const [id, req] of this.pending) {
|
|
388
616
|
if (req.epoch !== epoch) continue;
|
|
389
|
-
|
|
617
|
+
const error = new Error(`${exited} while waiting for ${req.method}`);
|
|
618
|
+
error.codexStderrTail = tail;
|
|
619
|
+
req.reject(error);
|
|
390
620
|
this.pending.delete(id);
|
|
391
621
|
}
|
|
622
|
+
if (this.pendingTurnCompletion) {
|
|
623
|
+
const turnId = this.pendingTurnCompletion.turnId ?? this._turnId;
|
|
624
|
+
this.dropUnechoedSteers(null, true);
|
|
625
|
+
this.eventHandler?.({
|
|
626
|
+
type: "turn_aborted",
|
|
627
|
+
reason: "process-exit",
|
|
628
|
+
...turnId ? { turn_id: turnId } : {},
|
|
629
|
+
error: { message: `${exited}${tail ? `: ${tail}` : ""}` }
|
|
630
|
+
});
|
|
631
|
+
}
|
|
392
632
|
this.resolvePendingTurn(true);
|
|
393
633
|
});
|
|
634
|
+
this.stderrRing = "";
|
|
635
|
+
this.steerUnsupported = false;
|
|
636
|
+
this.steeredAwaitingEcho.clear();
|
|
637
|
+
this.childThreadLabels.clear();
|
|
394
638
|
proc.stderr?.on("data", (chunk) => {
|
|
395
639
|
if (this.process !== proc || this.processEpoch !== epoch) return;
|
|
396
640
|
const text = chunk.toString().trim();
|
|
397
|
-
if (text)
|
|
641
|
+
if (text) {
|
|
642
|
+
logger.logger.debug("[CodexAppServer:stderr]", contentLogMetadata.contentLogMetadata(text));
|
|
643
|
+
this.stderrRing = `${this.stderrRing}
|
|
644
|
+
${text}`.slice(-400);
|
|
645
|
+
}
|
|
398
646
|
});
|
|
399
647
|
this.readline = node_readline.createInterface({ input: proc.stdout });
|
|
400
648
|
this.readline.on("line", (line) => {
|
|
@@ -425,26 +673,21 @@ class CodexAppServerClient {
|
|
|
425
673
|
logger.logger.debug(`[CodexAppServer] Disconnecting; pid=${pid ?? "none"}`);
|
|
426
674
|
this.readline?.close();
|
|
427
675
|
this.readline = null;
|
|
428
|
-
|
|
429
|
-
proc
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const killTimer = setTimeout(() => {
|
|
435
|
-
try {
|
|
436
|
-
process.kill(pid, 0);
|
|
437
|
-
process.kill(pid, "SIGKILL");
|
|
438
|
-
} catch {
|
|
439
|
-
}
|
|
440
|
-
}, 2e3);
|
|
441
|
-
killTimer.unref();
|
|
676
|
+
if (proc) {
|
|
677
|
+
stopCodexProcess(proc);
|
|
678
|
+
if (pid) {
|
|
679
|
+
const killTimer = setTimeout(() => killCodexProcessTree(proc), CodexAppServerClient.FORCE_KILL_AFTER_MS);
|
|
680
|
+
killTimer.unref();
|
|
681
|
+
}
|
|
442
682
|
}
|
|
443
683
|
this.process = null;
|
|
444
684
|
this.connected = false;
|
|
445
685
|
this._turnId = null;
|
|
446
686
|
this.notificationProtocol = "unknown";
|
|
447
687
|
this.completedTurnIds.clear();
|
|
688
|
+
this.failedTurnIds.clear();
|
|
689
|
+
this.lastTurnError = null;
|
|
690
|
+
this.clearThreadStopGrace();
|
|
448
691
|
if (!opts?.preserveThreadState) {
|
|
449
692
|
this._threadId = null;
|
|
450
693
|
this.threadDefaults = null;
|
|
@@ -454,6 +697,7 @@ class CodexAppServerClient {
|
|
|
454
697
|
req.reject(new Error(`Codex process disconnected while waiting for ${req.method}`));
|
|
455
698
|
this.pending.delete(id);
|
|
456
699
|
}
|
|
700
|
+
this.dropUnechoedSteers(null, true);
|
|
457
701
|
this.resolvePendingTurn(true);
|
|
458
702
|
if (this.sandboxCleanup) {
|
|
459
703
|
try {
|
|
@@ -657,20 +901,137 @@ class CodexAppServerClient {
|
|
|
657
901
|
// ─── Turn management ────────────────────────────────────────
|
|
658
902
|
/** Default grace period after interrupt before forcing a restart (ms). */
|
|
659
903
|
static ABORT_GRACE_MS = 3e3;
|
|
904
|
+
/** How long the graceful stop gets before the process tree is killed (disconnectInternal). */
|
|
905
|
+
static FORCE_KILL_AFTER_MS = 2e3;
|
|
660
906
|
hasPendingTurnCompletion() {
|
|
661
907
|
return this.pendingTurnCompletion !== null;
|
|
662
908
|
}
|
|
663
909
|
resolvePendingTurn(aborted) {
|
|
664
910
|
if (!this.pendingTurnCompletion) return;
|
|
665
911
|
this.clearTurnWatchdog();
|
|
912
|
+
this.clearThreadStopGrace();
|
|
666
913
|
this.pendingTurnCompletion.resolve(aborted);
|
|
667
914
|
this.pendingTurnCompletion = null;
|
|
915
|
+
this.releaseTurnOpenWaiters();
|
|
668
916
|
}
|
|
669
917
|
markPendingTurnStarted(turnId) {
|
|
670
918
|
if (!this.pendingTurnCompletion) return;
|
|
919
|
+
this.pendingTurnCompletion.opened = true;
|
|
671
920
|
if (turnId) {
|
|
672
921
|
this.pendingTurnCompletion.turnId = turnId;
|
|
673
922
|
}
|
|
923
|
+
this.releaseTurnOpenWaiters();
|
|
924
|
+
}
|
|
925
|
+
// ─── B-628: steering the running turn ───────────────────────
|
|
926
|
+
/** How long a steer waits for the pending turn to open before the message is queued instead. */
|
|
927
|
+
static STEER_OPEN_WAIT_MS = 5e3;
|
|
928
|
+
/** A turn Codex has reported running, under an id `turn/steer` can name. */
|
|
929
|
+
turnIsOpen() {
|
|
930
|
+
return this.pendingTurnCompletion?.opened === true && this._turnId !== null;
|
|
931
|
+
}
|
|
932
|
+
releaseTurnOpenWaiters() {
|
|
933
|
+
const waiters = this.turnOpenWaiters;
|
|
934
|
+
this.turnOpenWaiters = [];
|
|
935
|
+
for (const release of waiters) release();
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Waits for the pending turn to open — `turn/start` is in flight, or the runner is still
|
|
939
|
+
* bringing a released app-server back before sending it (B-461) — as Orca's send does
|
|
940
|
+
* (codex-structured-turn-open-wait.ts, 5 s). Resolves early when the turn ends instead.
|
|
941
|
+
*/
|
|
942
|
+
async waitForTurnOpen(timeoutMs) {
|
|
943
|
+
if (this.turnIsOpen()) return true;
|
|
944
|
+
if (timeoutMs <= 0) return false;
|
|
945
|
+
await new Promise((resolve) => {
|
|
946
|
+
const release = () => {
|
|
947
|
+
clearTimeout(timer);
|
|
948
|
+
resolve();
|
|
949
|
+
};
|
|
950
|
+
const timer = setTimeout(release, timeoutMs);
|
|
951
|
+
timer.unref();
|
|
952
|
+
this.turnOpenWaiters.push(release);
|
|
953
|
+
});
|
|
954
|
+
return this.turnIsOpen();
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* B-628: send a message into the turn running right now (`turn/steer`, codex ≥ 0.148 /
|
|
958
|
+
* app-server-protocol v2 `TurnSteerParams`). Codex appends it to that turn's pending input
|
|
959
|
+
* and the model reads it before its next call; the turn keeps its own model, approval
|
|
960
|
+
* policy and sandbox — the runner applies any change on the next `turn/start`. Codex
|
|
961
|
+
* refuses before taking any input (expectedTurnId is a precondition), so a non-`steered`
|
|
962
|
+
* outcome always means "queue it for the next turn as before": `no-turn` (nothing running,
|
|
963
|
+
* or it ended while we waited), `unsupported` (this app-server has no `turn/steer`,
|
|
964
|
+
* remembered until it is respawned), `refused` (Codex declined this one), `failed`
|
|
965
|
+
* (transport — the turn itself is reported through its own events).
|
|
966
|
+
*/
|
|
967
|
+
async steerTurn(input, opts) {
|
|
968
|
+
if (this.steerUnsupported) return "unsupported";
|
|
969
|
+
if (this.pendingInterrupt) return "no-turn";
|
|
970
|
+
const opened = await this.waitForTurnOpen(opts?.waitForTurnMs ?? CodexAppServerClient.STEER_OPEN_WAIT_MS);
|
|
971
|
+
const threadId = this._threadId;
|
|
972
|
+
const turnId = this._turnId;
|
|
973
|
+
if (!opened || !threadId || !turnId || !this.connected || this.pendingInterrupt) return "no-turn";
|
|
974
|
+
const params = {
|
|
975
|
+
threadId,
|
|
976
|
+
expectedTurnId: turnId,
|
|
977
|
+
...input.clientId ? { clientUserMessageId: input.clientId } : {},
|
|
978
|
+
input: [
|
|
979
|
+
{ type: "text", text: input.text },
|
|
980
|
+
...(input.images ?? []).map((path) => ({ type: "localImage", path }))
|
|
981
|
+
]
|
|
982
|
+
};
|
|
983
|
+
try {
|
|
984
|
+
const result = await this.request("turn/steer", params);
|
|
985
|
+
const steeredTurnId = typeof result?.turnId === "string" && result.turnId.length > 0 ? result.turnId : turnId;
|
|
986
|
+
if (input.clientId) this.steeredAwaitingEcho.set(input.clientId, steeredTurnId);
|
|
987
|
+
logger.logger.debug(`[CodexAppServer] Steered a message into turn ${steeredTurnId}`);
|
|
988
|
+
return "steered";
|
|
989
|
+
} catch (error) {
|
|
990
|
+
const code = jsonRpcErrorCodeOf(error);
|
|
991
|
+
if (code === -32601) {
|
|
992
|
+
this.steerUnsupported = true;
|
|
993
|
+
logger.logger.debug("[CodexAppServer] turn/steer is not supported by this codex; queuing instead");
|
|
994
|
+
return "unsupported";
|
|
995
|
+
}
|
|
996
|
+
if (code !== null) {
|
|
997
|
+
logger.logger.debug("[CodexAppServer] turn/steer refused:", contentLogMetadata.errorLogMetadata(error));
|
|
998
|
+
return "refused";
|
|
999
|
+
}
|
|
1000
|
+
logger.logger.debug("[CodexAppServer] turn/steer failed:", contentLogMetadata.errorLogMetadata(error));
|
|
1001
|
+
return "failed";
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
/** Default wait for Codex's `turn/completed` after its thread reported idle/systemError. */
|
|
1005
|
+
static THREAD_STOP_GRACE_MS = 1500;
|
|
1006
|
+
/**
|
|
1007
|
+
* B-625: the own thread stopped (idle / systemError) while a turn was pending and
|
|
1008
|
+
* had opened. Codex's `turn/completed` follows at once in every probed release
|
|
1009
|
+
* (0.154.0: 1 ms); this is the net under a release that does not send it, so the
|
|
1010
|
+
* session never waits on the silence watchdog for a turn Codex has already left.
|
|
1011
|
+
*/
|
|
1012
|
+
armThreadStopGrace(statusType) {
|
|
1013
|
+
this.clearThreadStopGrace();
|
|
1014
|
+
const graceMs = this.pendingTurnCompletion?.threadStopGraceMs ?? CodexAppServerClient.THREAD_STOP_GRACE_MS;
|
|
1015
|
+
this.threadStopGrace = setTimeout(() => {
|
|
1016
|
+
this.threadStopGrace = null;
|
|
1017
|
+
const pending = this.pendingTurnCompletion;
|
|
1018
|
+
if (!pending) return;
|
|
1019
|
+
const turnId = pending.turnId ?? this._turnId;
|
|
1020
|
+
const lastError = this.lastTurnError && (!turnId || !this.lastTurnError.turnId || this.lastTurnError.turnId === turnId) ? this.lastTurnError.error : null;
|
|
1021
|
+
logger.logger.warn(`[CodexAppServer] Thread went ${statusType} and no turn/completed followed for turn ${turnId ?? "?"}; ending the turn`);
|
|
1022
|
+
const failed = statusType === "systemError" || lastError !== null;
|
|
1023
|
+
this.emitRawTurnCompletion(
|
|
1024
|
+
turnId,
|
|
1025
|
+
failed ? "failed" : "completed",
|
|
1026
|
+
lastError ?? (failed ? { message: "Codex reported a system error and ended the turn without a verdict." } : null),
|
|
1027
|
+
`thread/status/changed:${statusType}`
|
|
1028
|
+
);
|
|
1029
|
+
}, graceMs);
|
|
1030
|
+
}
|
|
1031
|
+
clearThreadStopGrace() {
|
|
1032
|
+
if (!this.threadStopGrace) return;
|
|
1033
|
+
clearTimeout(this.threadStopGrace);
|
|
1034
|
+
this.threadStopGrace = null;
|
|
674
1035
|
}
|
|
675
1036
|
tryResolvePendingTurn(aborted, turnId, source) {
|
|
676
1037
|
const pending = this.pendingTurnCompletion;
|
|
@@ -773,6 +1134,8 @@ class CodexAppServerClient {
|
|
|
773
1134
|
}
|
|
774
1135
|
/** Default timeout for waiting on turn completion (ms). 10 minutes. */
|
|
775
1136
|
static TURN_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
1137
|
+
/** How much of the app-server's stderr is kept for its exit message. */
|
|
1138
|
+
static STDERR_TAIL_CHARS = 400;
|
|
776
1139
|
/**
|
|
777
1140
|
* Send a user turn and wait for it to complete (task_complete or turn_aborted).
|
|
778
1141
|
* Returns { aborted: true } if the turn was aborted (user cancel, permission reject, etc.).
|
|
@@ -787,8 +1150,11 @@ class CodexAppServerClient {
|
|
|
787
1150
|
const completion = new Promise((resolve) => {
|
|
788
1151
|
this.pendingTurnCompletion = {
|
|
789
1152
|
resolve,
|
|
790
|
-
turnId: null
|
|
1153
|
+
turnId: null,
|
|
1154
|
+
opened: false,
|
|
1155
|
+
threadStopGraceMs: opts?.threadStopGraceMs ?? CodexAppServerClient.THREAD_STOP_GRACE_MS
|
|
791
1156
|
};
|
|
1157
|
+
this.lastTurnError = null;
|
|
792
1158
|
this.armTurnWatchdog(timeoutMs);
|
|
793
1159
|
});
|
|
794
1160
|
try {
|
|
@@ -796,6 +1162,7 @@ class CodexAppServerClient {
|
|
|
796
1162
|
} catch (err) {
|
|
797
1163
|
this.clearTurnWatchdog();
|
|
798
1164
|
this.pendingTurnCompletion = null;
|
|
1165
|
+
this.releaseTurnOpenWaiters();
|
|
799
1166
|
throw err;
|
|
800
1167
|
}
|
|
801
1168
|
const aborted = await completion;
|
|
@@ -813,6 +1180,7 @@ class CodexAppServerClient {
|
|
|
813
1180
|
}
|
|
814
1181
|
logger.logger.warn(`[CodexAppServer] No notification from Codex for ${timeoutMs}ms \u2014 giving the turn up as aborted`);
|
|
815
1182
|
const turnId = this.pendingTurnCompletion.turnId ?? this._turnId;
|
|
1183
|
+
this.dropUnechoedSteers(turnId, true);
|
|
816
1184
|
this.eventHandler?.({
|
|
817
1185
|
type: "turn_aborted",
|
|
818
1186
|
reason: "timeout",
|
|
@@ -863,12 +1231,16 @@ class CodexAppServerClient {
|
|
|
863
1231
|
logger.logger.debug(
|
|
864
1232
|
`[CodexAppServer] Clearing thread state: thread=${this._threadId ?? "none"} turn=${this._turnId ?? "none"}`
|
|
865
1233
|
);
|
|
1234
|
+
this.dropUnechoedSteers(null, true);
|
|
866
1235
|
this.resolvePendingTurn(true);
|
|
867
1236
|
this._threadId = null;
|
|
868
1237
|
this._turnId = null;
|
|
869
1238
|
this.threadDefaults = null;
|
|
870
1239
|
this.completedTurnIds.clear();
|
|
1240
|
+
this.failedTurnIds.clear();
|
|
1241
|
+
this.lastTurnError = null;
|
|
871
1242
|
this.rawFileChangesByItemId.clear();
|
|
1243
|
+
this.childThreadLabels.clear();
|
|
872
1244
|
}
|
|
873
1245
|
// ─── JSON-RPC transport ─────────────────────────────────────
|
|
874
1246
|
/** Default timeout for RPC requests (ms). */
|
|
@@ -949,7 +1321,9 @@ class CodexAppServerClient {
|
|
|
949
1321
|
}
|
|
950
1322
|
this.pending.delete(msg.id);
|
|
951
1323
|
if (msg.error) {
|
|
952
|
-
|
|
1324
|
+
const error = new Error(`${pending.method}: ${msg.error.message} (code=${msg.error.code})`);
|
|
1325
|
+
if (typeof msg.error.code === "number") error.jsonRpcCode = msg.error.code;
|
|
1326
|
+
pending.reject(error);
|
|
953
1327
|
} else {
|
|
954
1328
|
pending.resolve(msg.result);
|
|
955
1329
|
}
|
|
@@ -1136,6 +1510,7 @@ class CodexAppServerClient {
|
|
|
1136
1510
|
}
|
|
1137
1511
|
if (method === "thread/started" || method === "turn/started" || method === "turn/completed" || method === "thread/status/changed") {
|
|
1138
1512
|
logger.logger.debug(`[CodexAppServer] Lifecycle notification: ${method}`);
|
|
1513
|
+
if (!this.isOwnThreadFrame(params)) return;
|
|
1139
1514
|
if (method === "turn/started") {
|
|
1140
1515
|
const turnId = this.extractTurnId(params);
|
|
1141
1516
|
if (turnId) {
|
|
@@ -1162,19 +1537,28 @@ class CodexAppServerClient {
|
|
|
1162
1537
|
logger.logger.debug(`[CodexAppServer] mcpServer startup status:`, params);
|
|
1163
1538
|
return;
|
|
1164
1539
|
}
|
|
1165
|
-
if (method === "error"
|
|
1540
|
+
if (method === "error") {
|
|
1541
|
+
if (!this.isOwnThreadFrame(params)) return;
|
|
1166
1542
|
const error = params?.error ?? {};
|
|
1167
|
-
const
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1543
|
+
const turnId = typeof params?.turnId === "string" && params.turnId.length > 0 ? params.turnId : null;
|
|
1544
|
+
if (params?.willRetry === true) {
|
|
1545
|
+
const message = typeof error.message === "string" ? error.message : "";
|
|
1546
|
+
const info = error.codexErrorInfo;
|
|
1547
|
+
const httpStatus = info && typeof info === "object" ? info.responseStreamDisconnected?.httpStatusCode ?? info.httpStatusCode ?? null : null;
|
|
1548
|
+
this.eventHandler?.({
|
|
1549
|
+
type: "turn_retry",
|
|
1550
|
+
message,
|
|
1551
|
+
...typeof httpStatus === "number" ? { http_status: httpStatus } : {},
|
|
1552
|
+
...turnId ? { turn_id: turnId } : {},
|
|
1553
|
+
...typeof error.additionalDetails === "string" ? { details: error.additionalDetails } : {}
|
|
1554
|
+
});
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
this.lastTurnError = { turnId, error };
|
|
1558
|
+
const pending = this.pendingTurnCompletion;
|
|
1559
|
+
if (pending && !pending.opened && (turnId === null || pending.turnId === null || pending.turnId === turnId)) {
|
|
1560
|
+
this.emitRawTurnCompletion(turnId ?? pending.turnId, "failed", error, "error:unopened-turn");
|
|
1561
|
+
}
|
|
1178
1562
|
return;
|
|
1179
1563
|
}
|
|
1180
1564
|
logger.logger.debug(`[CodexAppServer] Notification: ${method}`);
|