pneuma-skills 2.9.2 → 2.10.0
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/backends/codex/cli-launcher.ts +37 -66
- package/backends/codex/codex-adapter.ts +298 -54
- package/bin/pneuma-cli-helpers.ts +10 -0
- package/bin/pneuma.ts +341 -141
- package/core/types/shared-history.ts +36 -0
- package/core/types/viewer-contract.ts +3 -0
- package/dist/assets/{AgentBubble-CMZ6nRGR.js → AgentBubble-__Eijm5H.js} +1 -1
- package/dist/assets/{EditorPanel-BKjcAySx.js → EditorPanel-B68JETZf.js} +1 -1
- package/dist/assets/Launcher-DS0ACG8v.js +181 -0
- package/dist/assets/{ScaffoldConfirm-x0-O4HSf.js → ScaffoldConfirm-Bi_bMQAz.js} +1 -1
- package/dist/assets/{TerminalPanel-CbZ-jW0T.js → TerminalPanel-BuA7Rf9o.js} +1 -1
- package/dist/assets/{ar-SA-G6X2FPQ2-DVe6YYLO.js → ar-SA-G6X2FPQ2-B31Xfc_E.js} +1 -1
- package/dist/assets/{arc-B1rNScez.js → arc-CyuNsreF.js} +1 -1
- package/dist/assets/{az-AZ-76LH7QW2-hjS2RsZB.js → az-AZ-76LH7QW2-CuRnl-Sw.js} +1 -1
- package/dist/assets/{bg-BG-XCXSNQG7-CFR_M_N2.js → bg-BG-XCXSNQG7-BvVwWw75.js} +1 -1
- package/dist/assets/{blockDiagram-38ab4fdb-BHHquyco.js → blockDiagram-38ab4fdb-DSws9v7M.js} +1 -1
- package/dist/assets/{bn-BD-2XOGV67Q-kqviDHJf.js → bn-BD-2XOGV67Q-g12DVKj5.js} +1 -1
- package/dist/assets/{c4Diagram-3d4e48cf-BjYeH1AJ.js → c4Diagram-3d4e48cf-ElmVreVH.js} +1 -1
- package/dist/assets/{ca-ES-6MX7JW3Y-DZrWV-vj.js → ca-ES-6MX7JW3Y-C7qPODeC.js} +1 -1
- package/dist/assets/channel-DiGiDB_L.js +1 -0
- package/dist/assets/{classDiagram-70f12bd4-Ce-UuQMw.js → classDiagram-70f12bd4-D8io7OAO.js} +1 -1
- package/dist/assets/{classDiagram-v2-f2320105-pke9s22r.js → classDiagram-v2-f2320105-Dx0FZcpH.js} +1 -1
- package/dist/assets/clone-XIS6rxyv.js +1 -0
- package/dist/assets/{createText-2e5e7dd3-ComqHWqI.js → createText-2e5e7dd3-plk4eMFK.js} +1 -1
- package/dist/assets/{cs-CZ-2BRQDIVT-BPSet93s.js → cs-CZ-2BRQDIVT-gZnhjLjt.js} +1 -1
- package/dist/assets/{da-DK-5WZEPLOC-kBC4S0XX.js → da-DK-5WZEPLOC-DH3OzKw5.js} +1 -1
- package/dist/assets/{de-DE-XR44H4JA-DXZWcb7v.js → de-DE-XR44H4JA-Cupdzsmc.js} +1 -1
- package/dist/assets/{download-D43IpEvW.js → download-DW7Va2rq.js} +1 -1
- package/dist/assets/{edges-e0da2a9e-j1tunLqf.js → edges-e0da2a9e-B3wCpawR.js} +1 -1
- package/dist/assets/{el-GR-BZB4AONW-DzEWI2pA.js → el-GR-BZB4AONW-Cx2YGl1B.js} +1 -1
- package/dist/assets/{erDiagram-9861fffd-C_T5c7_d.js → erDiagram-9861fffd-DZb53j60.js} +1 -1
- package/dist/assets/{es-ES-U4NZUMDT-C_C9YIWR.js → es-ES-U4NZUMDT-BhLuQ3hX.js} +1 -1
- package/dist/assets/{eu-ES-A7QVB2H4-DhHf-elD.js → eu-ES-A7QVB2H4-CCqNLi6t.js} +1 -1
- package/dist/assets/{fa-IR-HGAKTJCU-BaYY2OWl.js → fa-IR-HGAKTJCU-DLoACiBg.js} +1 -1
- package/dist/assets/{fi-FI-Z5N7JZ37-C8LCaGJm.js → fi-FI-Z5N7JZ37-CMACT2Jo.js} +1 -1
- package/dist/assets/{flowDb-956e92f1-D4hCuAgC.js → flowDb-956e92f1-DFYkVFcK.js} +1 -1
- package/dist/assets/{flowDiagram-66a62f08-BXSaIGhX.js → flowDiagram-66a62f08-DY9RC_-P.js} +1 -1
- package/dist/assets/flowDiagram-v2-96b9c2cf-6HAI7biC.js +1 -0
- package/dist/assets/{flowchart-elk-definition-4a651766-Dtvii5ag.js → flowchart-elk-definition-4a651766-DABg42Jp.js} +1 -1
- package/dist/assets/{fr-FR-RHASNOE6-D5lnyYip.js → fr-FR-RHASNOE6-DFyLSpAT.js} +1 -1
- package/dist/assets/{ganttDiagram-c361ad54-CZW5pQtt.js → ganttDiagram-c361ad54-BPqRq4-e.js} +1 -1
- package/dist/assets/{gitGraphDiagram-72cf32ee-BPpX5Zhn.js → gitGraphDiagram-72cf32ee-BqFi25_h.js} +1 -1
- package/dist/assets/{gl-ES-HMX3MZ6V-BGFbJfwN.js → gl-ES-HMX3MZ6V-PWHfuqfi.js} +1 -1
- package/dist/assets/{graph-DCVnljON.js → graph-COOWca2o.js} +1 -1
- package/dist/assets/{he-IL-6SHJWFNN-BUrDpH0Z.js → he-IL-6SHJWFNN-B0RoZp-b.js} +1 -1
- package/dist/assets/{hi-IN-IWLTKZ5I-DGA5P67V.js → hi-IN-IWLTKZ5I-DYxveKtS.js} +1 -1
- package/dist/assets/{hu-HU-A5ZG7DT2-B_ZZ0L6t.js → hu-HU-A5ZG7DT2-C483obeo.js} +1 -1
- package/dist/assets/{id-ID-SAP4L64H-CoPBEkck.js → id-ID-SAP4L64H-DIskCvbs.js} +1 -1
- package/dist/assets/{index-3862675e-B7FzOmHC.js → index-3862675e-MJppC4_3.js} +1 -1
- package/dist/assets/index-CW9op-ib.css +1 -0
- package/dist/assets/index-CdojEznK.js +1 -0
- package/dist/assets/{index-BjMHnCI7.js → index-CsCQmMXN.js} +1 -1
- package/dist/assets/{index-CCw_JpPQ.js → index-DOqJ5Cop.js} +1 -1
- package/dist/assets/index-DvB1CG8n.js +83 -0
- package/dist/assets/{index-h13dvvWW.js → index-Ogq0Y-Kp.js} +4 -4
- package/dist/assets/{infoDiagram-f8f76790-Czkw44zX.js → infoDiagram-f8f76790-1mwP_oYq.js} +1 -1
- package/dist/assets/{it-IT-JPQ66NNP-BMI1jVUV.js → it-IT-JPQ66NNP-CWrOj79b.js} +1 -1
- package/dist/assets/{ja-JP-DBVTYXUO-DSn8KL95.js → ja-JP-DBVTYXUO-BjB5xP6P.js} +1 -1
- package/dist/assets/{journeyDiagram-49397b02-D6l2GcOV.js → journeyDiagram-49397b02-s2FFQM88.js} +1 -1
- package/dist/assets/{kaa-6HZHGXH3-DkgEVo7W.js → kaa-6HZHGXH3-DRQ02w-P.js} +1 -1
- package/dist/assets/{kab-KAB-ZGHBKWFO-Dg75DA4e.js → kab-KAB-ZGHBKWFO-BIqWHIO7.js} +1 -1
- package/dist/assets/{kk-KZ-P5N5QNE5-DhHfX2yZ.js → kk-KZ-P5N5QNE5-BeB8Lkyi.js} +1 -1
- package/dist/assets/{km-KH-HSX4SM5Z-DhL7IOb4.js → km-KH-HSX4SM5Z-BPBAMdUj.js} +1 -1
- package/dist/assets/{ko-KR-MTYHY66A-B8LamGPN.js → ko-KR-MTYHY66A-DCDKpapW.js} +1 -1
- package/dist/assets/{ku-TR-6OUDTVRD-C-Ue4acf.js → ku-TR-6OUDTVRD-CVp6QVXq.js} +1 -1
- package/dist/assets/{layout-DBk3JhP2.js → layout-Ch_it_de.js} +1 -1
- package/dist/assets/{line-C2fwjjZs.js → line-BWivg3b0.js} +1 -1
- package/dist/assets/{lt-LT-XHIRWOB4-Cx_9I2Jp.js → lt-LT-XHIRWOB4-BENmkKFE.js} +1 -1
- package/dist/assets/{lv-LV-5QDEKY6T-DA1eV67H.js → lv-LV-5QDEKY6T-Blzv1Vol.js} +1 -1
- package/dist/assets/{mindmap-definition-fc14e90a-BJO6W_jl.js → mindmap-definition-fc14e90a-D1peR19j.js} +1 -1
- package/dist/assets/{mr-IN-CRQNXWMA-DD5P0SdJ.js → mr-IN-CRQNXWMA-B0N3Ayye.js} +1 -1
- package/dist/assets/{my-MM-5M5IBNSE-BbRyaobI.js → my-MM-5M5IBNSE-CrkOwPg6.js} +1 -1
- package/dist/assets/{nb-NO-T6EIAALU-BPUPMfp7.js → nb-NO-T6EIAALU-DD2piqGe.js} +1 -1
- package/dist/assets/{nl-NL-IS3SIHDZ-BLhlmQlY.js → nl-NL-IS3SIHDZ-evJQTIqF.js} +1 -1
- package/dist/assets/{nn-NO-6E72VCQL-CEvmCYDT.js → nn-NO-6E72VCQL-BIP8cljI.js} +1 -1
- package/dist/assets/{oc-FR-POXYY2M6-CzSpprL8.js → oc-FR-POXYY2M6-BN1d5Rtx.js} +1 -1
- package/dist/assets/{pa-IN-N4M65BXN-DEq6_MT9.js → pa-IN-N4M65BXN-BIe2bTHi.js} +1 -1
- package/dist/assets/{percentages-BXMCSKIN-D4QRkYSh.js → percentages-BXMCSKIN-Bc9S0tLg.js} +7 -7
- package/dist/assets/{pica-CJ7DRNvr.js → pica-Zwtvbd6o.js} +1 -1
- package/dist/assets/{pieDiagram-8a3498a8-CbcnHnE0.js → pieDiagram-8a3498a8-BFj8vX7l.js} +1 -1
- package/dist/assets/{pl-PL-T2D74RX3-DaxlnfpV.js → pl-PL-T2D74RX3-WT-OH4Vx.js} +1 -1
- package/dist/assets/{pneuma-mode-DgITUhq7.js → pneuma-mode-5X4Ybp9D.js} +1 -1
- package/dist/assets/{pneuma-mode-C9MJWf55.js → pneuma-mode-BKKOs-Nz.js} +1 -1
- package/dist/assets/pneuma-mode-Bw4FCf31.js +206 -0
- package/dist/assets/pneuma-mode-Ca9v8rvf.js +121 -0
- package/dist/assets/pneuma-mode-Dtx56YCa.js +17 -0
- package/dist/assets/pneuma-mode-RXAmMHRv.js +55 -0
- package/dist/assets/pneuma-mode-mVbaQ1QQ.js +10 -0
- package/dist/assets/{pt-BR-5N22H2LF-COlvU6eY.js → pt-BR-5N22H2LF-BUj6MuNp.js} +1 -1
- package/dist/assets/{pt-PT-UZXXM6DQ-Bu-UzvMu.js → pt-PT-UZXXM6DQ-BGv79upc.js} +1 -1
- package/dist/assets/{quadrantDiagram-120e2f19-DqZozxoa.js → quadrantDiagram-120e2f19-BeMo99TY.js} +1 -1
- package/dist/assets/{rasterize-34PCWURX-CfC5MoKT.js → rasterize-34PCWURX-BaZQqXEK.js} +1 -1
- package/dist/assets/{requirementDiagram-deff3bca-zdeZ5Zog.js → requirementDiagram-deff3bca-bu6gWiTD.js} +1 -1
- package/dist/assets/{ro-RO-JPDTUUEW-DNitNPKU.js → ro-RO-JPDTUUEW-CkhJhPA3.js} +1 -1
- package/dist/assets/{ru-RU-B4JR7IUQ-Dl-hvBe3.js → ru-RU-B4JR7IUQ-CDYvP590.js} +1 -1
- package/dist/assets/{sankeyDiagram-04a897e0-C0DId110.js → sankeyDiagram-04a897e0-BUANQW8A.js} +1 -1
- package/dist/assets/{sequenceDiagram-704730f1-CVL3EuWN.js → sequenceDiagram-704730f1-fRgSXUYf.js} +1 -1
- package/dist/assets/{si-LK-N5RQ5JYF-PrDOff3R.js → si-LK-N5RQ5JYF-DE4sIjlD.js} +1 -1
- package/dist/assets/{sk-SK-C5VTKIMK-D5cbWUua.js → sk-SK-C5VTKIMK-BLSxc3FY.js} +1 -1
- package/dist/assets/{sl-SI-NN7IZMDC-BadZqU8N.js → sl-SI-NN7IZMDC-Bfoj6QKN.js} +1 -1
- package/dist/assets/{stateDiagram-587899a1-BKiEXEIi.js → stateDiagram-587899a1-Cl58Yh8S.js} +1 -1
- package/dist/assets/{stateDiagram-v2-d93cdb3a-DTRzXuJv.js → stateDiagram-v2-d93cdb3a-BIt4qTgB.js} +1 -1
- package/dist/assets/{styles-6aaf32cf-6a0OV-Y-.js → styles-6aaf32cf-DRKwI1HK.js} +1 -1
- package/dist/assets/{styles-9a916d00-By4qztKe.js → styles-9a916d00-BoYeNSQs.js} +1 -1
- package/dist/assets/{styles-c10674c1-ntQTVRhG.js → styles-c10674c1-CZwzG4ch.js} +1 -1
- package/dist/assets/{subset-shared.chunk-BNYnoFpQ.js → subset-shared.chunk-DQlwBXHF.js} +1 -1
- package/dist/assets/{subset-worker.chunk-42iRwKgB.js → subset-worker.chunk-BVdWhVxw.js} +1 -1
- package/dist/assets/{sv-SE-XGPEYMSR-YOWLd5HQ.js → sv-SE-XGPEYMSR-CW5g8xRj.js} +1 -1
- package/dist/assets/{svgDrawCommon-08f97a94-BNShuOxL.js → svgDrawCommon-08f97a94-r7JHvos9.js} +1 -1
- package/dist/assets/{ta-IN-2NMHFXQM-DZ33CX4J.js → ta-IN-2NMHFXQM-rcxe7Aeh.js} +1 -1
- package/dist/assets/{th-TH-HPSO5L25-C__ogmor.js → th-TH-HPSO5L25-DoxgcHtt.js} +1 -1
- package/dist/assets/{timeline-definition-85554ec2-CnzsfCW9.js → timeline-definition-85554ec2-B1DmjfE2.js} +1 -1
- package/dist/assets/{toBlob-D6gas9M8.js → toBlob-JVl8QmBd.js} +1 -1
- package/dist/assets/{toCanvas-cdXr-2CR.js → toCanvas-Cytf3jkU.js} +1 -1
- package/dist/assets/{toImg-sFpicrMi.js → toImg-DMYlIQ1b.js} +1 -1
- package/dist/assets/{tr-TR-DEFEU3FU-6R7C_oA2.js → tr-TR-DEFEU3FU-lmn4Z-wR.js} +1 -1
- package/dist/assets/{uk-UA-QMV73CPH-CvolgTsC.js → uk-UA-QMV73CPH-rEOe_8mf.js} +1 -1
- package/dist/assets/{use-resilient-parse-EL9obkgJ.js → use-resilient-parse-cRas0DPn.js} +1 -1
- package/dist/assets/{vi-VN-M7AON7JQ-CTg63Crb.js → vi-VN-M7AON7JQ-mcIk0V-g.js} +1 -1
- package/dist/assets/{with-selector-DSJqZOiL.js → with-selector-CRZMlSge.js} +1 -1
- package/dist/assets/{xychartDiagram-e933f94c-C09MuqVP.js → xychartDiagram-e933f94c-DPshm1e0.js} +1 -1
- package/dist/assets/{zh-CN-LNUGB5OW-C0KLOptS.js → zh-CN-LNUGB5OW-BdZAXT9j.js} +1 -1
- package/dist/assets/{zh-HK-E62DVLB3-DTMwO0Uu.js → zh-HK-E62DVLB3-DVXSOPNN.js} +1 -1
- package/dist/assets/{zh-TW-RAJ6MFWO-DQvA3jgh.js → zh-TW-RAJ6MFWO-C2cX2bOn.js} +1 -1
- package/dist/index.html +2 -2
- package/modes/doc/viewer/DocPreview.tsx +12 -5
- package/modes/draw/viewer/DrawPreview.tsx +14 -5
- package/modes/illustrate/viewer/IllustratePreview.tsx +15 -8
- package/modes/slide/viewer/SlidePreview.tsx +16 -8
- package/modes/webcraft/viewer/WebPreview.tsx +17 -9
- package/package.json +1 -1
- package/server/__tests__/history-export.test.ts +76 -0
- package/server/__tests__/history-import.test.ts +77 -0
- package/server/__tests__/history-summary.test.ts +42 -0
- package/server/__tests__/replay-continue.test.ts +74 -0
- package/server/__tests__/shadow-git.test.ts +130 -0
- package/server/history-export.ts +173 -0
- package/server/history-import.ts +123 -0
- package/server/history-summary.ts +58 -0
- package/server/index.ts +428 -10
- package/server/replay-continue.ts +95 -0
- package/server/shadow-git.ts +154 -0
- package/server/share.ts +222 -0
- package/server/skill-installer.ts +42 -0
- package/server/ws-bridge-codex.ts +7 -0
- package/server/ws-bridge.ts +16 -3
- package/snapshot/history-share.ts +61 -0
- package/src/App.tsx +43 -25
- package/src/components/ChatPanel.tsx +15 -10
- package/src/components/Launcher.tsx +515 -9
- package/src/components/ReplayPlayer.tsx +162 -0
- package/src/components/TopBar.tsx +213 -6
- package/src/index.css +10 -0
- package/src/replay-engine.ts +281 -0
- package/src/store/index.ts +2 -0
- package/src/store/replay-slice.ts +72 -0
- package/src/store/types.ts +3 -1
- package/src/ws.ts +2 -0
- package/dist/assets/Launcher-Cjx0jp6x.js +0 -181
- package/dist/assets/channel-UEITcOsU.js +0 -1
- package/dist/assets/clone-BQRaJdPT.js +0 -1
- package/dist/assets/flowDiagram-v2-96b9c2cf-DUyY1urJ.js +0 -1
- package/dist/assets/index-C_KSF44-.js +0 -82
- package/dist/assets/index-CbMCOwb0.js +0 -1
- package/dist/assets/index-D9JIQN4c.css +0 -1
- package/dist/assets/pneuma-mode-4PHb7CVE.js +0 -55
- package/dist/assets/pneuma-mode-Br-ZcgvO.js +0 -206
- package/dist/assets/pneuma-mode-CvVPZhJH.js +0 -121
- package/dist/assets/pneuma-mode-D01AH-3X.js +0 -10
- package/dist/assets/pneuma-mode-LOMw9sgF.js +0 -17
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { resolve, join, delimiter } from "node:path";
|
|
10
10
|
import { existsSync, realpathSync, readFileSync } from "node:fs";
|
|
11
|
-
import type
|
|
11
|
+
import { spawn as nodeSpawn, type ChildProcess } from "node:child_process";
|
|
12
12
|
import { resolveBinary, getEnrichedPath } from "../../server/path-resolver.js";
|
|
13
13
|
import { CodexAdapter, StdioTransport } from "./codex-adapter.js";
|
|
14
14
|
import type { CodexAdapterOptions } from "./codex-adapter.js";
|
|
@@ -64,7 +64,7 @@ function isTextScript(filePath: string): boolean {
|
|
|
64
64
|
*/
|
|
65
65
|
export class CodexCliLauncher {
|
|
66
66
|
private sessions = new Map<string, CodexSessionInfo>();
|
|
67
|
-
private
|
|
67
|
+
private nodeProcesses = new Map<string, ChildProcess>();
|
|
68
68
|
private adapters = new Map<string, CodexAdapter>();
|
|
69
69
|
private exitHandlers: ((sessionId: string, exitCode: number | null) => void)[] = [];
|
|
70
70
|
private adapterCreatedHandlers: ((sessionId: string, adapter: CodexAdapter) => void)[] = [];
|
|
@@ -151,21 +151,26 @@ export class CodexCliLauncher {
|
|
|
151
151
|
|
|
152
152
|
console.log(`[codex-launcher] Spawning session ${sessionId}: ${spawnCmd.join(" ")}`);
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
// Use node:child_process instead of Bun.spawn to avoid Bun's ReadableStream
|
|
155
|
+
// prematurely closing stdout while the process is still alive.
|
|
156
|
+
const cleanEnv: Record<string, string> = {};
|
|
157
|
+
for (const [k, v] of Object.entries(spawnEnv)) {
|
|
158
|
+
if (v !== undefined) cleanEnv[k] = v;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const nodeProc = nodeSpawn(spawnCmd[0], spawnCmd.slice(1), {
|
|
155
162
|
cwd: info.cwd,
|
|
156
|
-
env:
|
|
157
|
-
|
|
158
|
-
stdout: "pipe",
|
|
159
|
-
stderr: "pipe",
|
|
163
|
+
env: cleanEnv,
|
|
164
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
160
165
|
});
|
|
161
166
|
|
|
162
|
-
info.pid =
|
|
163
|
-
this.
|
|
167
|
+
info.pid = nodeProc.pid;
|
|
168
|
+
this.nodeProcesses.set(sessionId, nodeProc);
|
|
164
169
|
|
|
165
|
-
//
|
|
166
|
-
|
|
170
|
+
// Create a StdioTransport from Node.js streams
|
|
171
|
+
const transport = StdioTransport.fromNodeStreams(nodeProc.stdin!, nodeProc.stdout!);
|
|
167
172
|
|
|
168
|
-
// Create adapter
|
|
173
|
+
// Create adapter with the transport directly
|
|
169
174
|
const adapterOptions: CodexAdapterOptions = {
|
|
170
175
|
model: options.model,
|
|
171
176
|
cwd: info.cwd,
|
|
@@ -174,16 +179,16 @@ export class CodexCliLauncher {
|
|
|
174
179
|
threadId: options.resumeThreadId,
|
|
175
180
|
killProcess: async () => {
|
|
176
181
|
try {
|
|
177
|
-
|
|
178
|
-
await Promise
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
+
nodeProc.kill("SIGTERM");
|
|
183
|
+
await new Promise<void>((resolve) => {
|
|
184
|
+
const timer = setTimeout(() => { nodeProc.kill("SIGKILL"); resolve(); }, 5000);
|
|
185
|
+
nodeProc.once("exit", () => { clearTimeout(timer); resolve(); });
|
|
186
|
+
});
|
|
182
187
|
} catch {}
|
|
183
188
|
},
|
|
184
189
|
};
|
|
185
190
|
|
|
186
|
-
const adapter = new CodexAdapter(
|
|
191
|
+
const adapter = new CodexAdapter(transport, sessionId, adapterOptions);
|
|
187
192
|
this.adapters.set(sessionId, adapter);
|
|
188
193
|
|
|
189
194
|
// Wire adapter callbacks
|
|
@@ -211,14 +216,15 @@ export class CodexCliLauncher {
|
|
|
211
216
|
}
|
|
212
217
|
|
|
213
218
|
// Monitor process exit
|
|
214
|
-
|
|
215
|
-
console.log(`[codex-launcher] Session ${sessionId} exited (code=${exitCode})`);
|
|
219
|
+
nodeProc.once("exit", (exitCode) => {
|
|
216
220
|
const session = this.sessions.get(sessionId);
|
|
221
|
+
const uptime = session ? Math.round((Date.now() - session.createdAt) / 1000) : 0;
|
|
222
|
+
console.error(`[codex-launcher] Session ${sessionId} process exited (code=${exitCode}, uptime=${uptime}s, state=${session?.state})`);
|
|
217
223
|
if (session) {
|
|
218
224
|
session.state = "exited";
|
|
219
225
|
session.exitCode = exitCode;
|
|
220
226
|
}
|
|
221
|
-
this.
|
|
227
|
+
this.nodeProcesses.delete(sessionId);
|
|
222
228
|
this.adapters.delete(sessionId);
|
|
223
229
|
for (const handler of this.exitHandlers) {
|
|
224
230
|
try { handler(sessionId, exitCode); } catch {}
|
|
@@ -260,58 +266,23 @@ export class CodexCliLauncher {
|
|
|
260
266
|
this.adapters.delete(sessionId);
|
|
261
267
|
}
|
|
262
268
|
|
|
263
|
-
const
|
|
264
|
-
if (!
|
|
265
|
-
|
|
266
|
-
if (process.platform === "win32") {
|
|
267
|
-
proc.kill();
|
|
268
|
-
} else {
|
|
269
|
-
proc.kill("SIGTERM");
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const exited = await Promise.race([
|
|
273
|
-
proc.exited.then(() => true),
|
|
274
|
-
new Promise<false>((resolve) => setTimeout(() => resolve(false), 5_000)),
|
|
275
|
-
]);
|
|
269
|
+
const nodeProc = this.nodeProcesses.get(sessionId);
|
|
270
|
+
if (!nodeProc) return false;
|
|
276
271
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
}
|
|
272
|
+
nodeProc.kill("SIGTERM");
|
|
273
|
+
await new Promise<void>((resolve) => {
|
|
274
|
+
const timer = setTimeout(() => { nodeProc.kill("SIGKILL"); resolve(); }, 5000);
|
|
275
|
+
nodeProc.once("exit", () => { clearTimeout(timer); resolve(); });
|
|
276
|
+
});
|
|
284
277
|
|
|
285
278
|
const session = this.sessions.get(sessionId);
|
|
286
|
-
if (session) {
|
|
287
|
-
|
|
288
|
-
session.exitCode = -1;
|
|
289
|
-
}
|
|
290
|
-
this.processes.delete(sessionId);
|
|
279
|
+
if (session) { session.state = "exited"; session.exitCode = -1; }
|
|
280
|
+
this.nodeProcesses.delete(sessionId);
|
|
291
281
|
return true;
|
|
292
282
|
}
|
|
293
283
|
|
|
294
284
|
async killAll(): Promise<void> {
|
|
295
|
-
const ids = [...this.
|
|
285
|
+
const ids = [...this.nodeProcesses.keys()];
|
|
296
286
|
await Promise.all(ids.map((id) => this.kill(id)));
|
|
297
287
|
}
|
|
298
|
-
|
|
299
|
-
private async pipeStderr(sessionId: string, proc: Subprocess): Promise<void> {
|
|
300
|
-
const stderr = proc.stderr;
|
|
301
|
-
if (!stderr || typeof stderr === "number") return;
|
|
302
|
-
const reader = (stderr as ReadableStream<Uint8Array>).getReader();
|
|
303
|
-
const decoder = new TextDecoder();
|
|
304
|
-
try {
|
|
305
|
-
while (true) {
|
|
306
|
-
const { done, value } = await reader.read();
|
|
307
|
-
if (done) break;
|
|
308
|
-
const text = decoder.decode(value);
|
|
309
|
-
if (text.trim()) {
|
|
310
|
-
console.error(`[codex:${sessionId}:stderr] ${text.trimEnd()}`);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
} catch {
|
|
314
|
-
// stream closed
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
288
|
}
|
|
@@ -88,28 +88,90 @@ export class StdioTransport implements ICodexTransport {
|
|
|
88
88
|
private pendingTimers = new Map<number, ReturnType<typeof setTimeout>>();
|
|
89
89
|
private notificationHandler: ((method: string, params: Record<string, unknown>) => void) | null = null;
|
|
90
90
|
private requestHandler: ((method: string, id: number, params: Record<string, unknown>) => void) | null = null;
|
|
91
|
-
|
|
91
|
+
/** Node.js Writable stream for stdin. */
|
|
92
|
+
private nodeStdin: import("node:stream").Writable | null = null;
|
|
93
|
+
/** Fallback WritableStream writer for non-Node stdin (tests). */
|
|
94
|
+
private writer: WritableStreamDefaultWriter<Uint8Array> | null = null;
|
|
92
95
|
private connected = true;
|
|
93
96
|
private buffer = "";
|
|
94
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Constructor for test usage with WritableStream/ReadableStream.
|
|
100
|
+
* Production code should use `StdioTransport.fromNodeStreams()`.
|
|
101
|
+
*/
|
|
95
102
|
constructor(
|
|
96
103
|
stdin: WritableStream<Uint8Array> | { write(data: Uint8Array): number },
|
|
97
104
|
stdout: ReadableStream<Uint8Array>,
|
|
98
105
|
) {
|
|
99
|
-
let writable: WritableStream<Uint8Array>;
|
|
100
106
|
if ("write" in stdin && typeof stdin.write === "function") {
|
|
101
|
-
writable = new WritableStream({
|
|
107
|
+
const writable = new WritableStream({
|
|
102
108
|
write(chunk) {
|
|
103
109
|
(stdin as { write(data: Uint8Array): number }).write(chunk);
|
|
104
110
|
},
|
|
105
111
|
});
|
|
112
|
+
this.writer = writable.getWriter();
|
|
106
113
|
} else {
|
|
107
|
-
|
|
114
|
+
this.writer = (stdin as WritableStream<Uint8Array>).getWriter();
|
|
108
115
|
}
|
|
109
|
-
this.writer = writable.getWriter();
|
|
110
116
|
this.readStdout(stdout);
|
|
111
117
|
}
|
|
112
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Create a StdioTransport from Node.js child_process streams.
|
|
121
|
+
* Avoids Bun's ReadableStream bug where proc.stdout prematurely closes.
|
|
122
|
+
*/
|
|
123
|
+
static fromNodeStreams(
|
|
124
|
+
stdin: import("node:stream").Writable,
|
|
125
|
+
stdout: import("node:stream").Readable,
|
|
126
|
+
): StdioTransport {
|
|
127
|
+
const transport = Object.create(StdioTransport.prototype) as StdioTransport;
|
|
128
|
+
transport.nextId = 1;
|
|
129
|
+
transport.pending = new Map();
|
|
130
|
+
transport.pendingTimers = new Map();
|
|
131
|
+
transport.notificationHandler = null;
|
|
132
|
+
transport.requestHandler = null;
|
|
133
|
+
transport.stdinSink = null;
|
|
134
|
+
transport.writer = null;
|
|
135
|
+
transport.nodeStdin = stdin;
|
|
136
|
+
transport.connected = true;
|
|
137
|
+
transport.buffer = "";
|
|
138
|
+
transport.readNodeStdout(stdout);
|
|
139
|
+
return transport;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private readNodeStdout(stdout: import("node:stream").Readable): void {
|
|
143
|
+
stdout.on("data", (chunk: Buffer) => {
|
|
144
|
+
this.buffer += chunk.toString("utf-8");
|
|
145
|
+
this.processBuffer();
|
|
146
|
+
});
|
|
147
|
+
stdout.on("end", () => {
|
|
148
|
+
console.error(`[codex-adapter] Node stdout stream ended`);
|
|
149
|
+
this.closeTransport();
|
|
150
|
+
});
|
|
151
|
+
stdout.on("error", (err) => {
|
|
152
|
+
console.error(`[codex-adapter] Node stdout error:`, err);
|
|
153
|
+
this.closeTransport();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private closeTransport(): void {
|
|
158
|
+
if (!this.connected) return;
|
|
159
|
+
const pendingCount = this.pending.size;
|
|
160
|
+
if (pendingCount > 0) {
|
|
161
|
+
console.error(`[codex-adapter] Transport closed with ${pendingCount} pending RPC call(s)`);
|
|
162
|
+
}
|
|
163
|
+
this.connected = false;
|
|
164
|
+
for (const [, timer] of this.pendingTimers) {
|
|
165
|
+
clearTimeout(timer);
|
|
166
|
+
}
|
|
167
|
+
this.pendingTimers.clear();
|
|
168
|
+
for (const [, { reject }] of this.pending) {
|
|
169
|
+
reject(new Error("Transport closed"));
|
|
170
|
+
}
|
|
171
|
+
this.pending.clear();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Read from a web ReadableStream (used by tests; production uses readNodeStdout). */
|
|
113
175
|
private async readStdout(stdout: ReadableStream<Uint8Array>): Promise<void> {
|
|
114
176
|
const reader = stdout.getReader();
|
|
115
177
|
const decoder = new TextDecoder();
|
|
@@ -123,15 +185,7 @@ export class StdioTransport implements ICodexTransport {
|
|
|
123
185
|
} catch (err) {
|
|
124
186
|
console.error("[codex-adapter] stdout reader error:", err);
|
|
125
187
|
} finally {
|
|
126
|
-
this.
|
|
127
|
-
for (const [, timer] of this.pendingTimers) {
|
|
128
|
-
clearTimeout(timer);
|
|
129
|
-
}
|
|
130
|
-
this.pendingTimers.clear();
|
|
131
|
-
for (const [, { reject }] of this.pending) {
|
|
132
|
-
reject(new Error("Transport closed"));
|
|
133
|
-
}
|
|
134
|
-
this.pending.clear();
|
|
188
|
+
this.closeTransport();
|
|
135
189
|
}
|
|
136
190
|
}
|
|
137
191
|
|
|
@@ -232,7 +286,18 @@ export class StdioTransport implements ICodexTransport {
|
|
|
232
286
|
if (!this.connected) {
|
|
233
287
|
throw new Error("Transport closed");
|
|
234
288
|
}
|
|
235
|
-
|
|
289
|
+
if (this.nodeStdin) {
|
|
290
|
+
return new Promise<void>((resolve, reject) => {
|
|
291
|
+
this.nodeStdin!.write(data, "utf-8", (err) => {
|
|
292
|
+
if (err) reject(err); else resolve();
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
if (this.writer) {
|
|
297
|
+
await this.writer.write(new TextEncoder().encode(data));
|
|
298
|
+
} else {
|
|
299
|
+
throw new Error("No stdin writer available");
|
|
300
|
+
}
|
|
236
301
|
}
|
|
237
302
|
}
|
|
238
303
|
|
|
@@ -460,9 +525,15 @@ export class CodexAdapter {
|
|
|
460
525
|
capabilities: {
|
|
461
526
|
experimentalApi: true,
|
|
462
527
|
},
|
|
463
|
-
}) as { serverInfo?: { name?: string; version?: string } } | undefined;
|
|
464
|
-
|
|
465
|
-
|
|
528
|
+
}) as { serverInfo?: { name?: string; version?: string }; userAgent?: string } | undefined;
|
|
529
|
+
|
|
530
|
+
// v0.114+: response has `userAgent` instead of `serverInfo`
|
|
531
|
+
let serverVersion = initResult?.serverInfo?.version || "";
|
|
532
|
+
if (!serverVersion && initResult?.userAgent) {
|
|
533
|
+
// Parse version from userAgent string like "pneuma-skills/0.114.0 (...)"
|
|
534
|
+
const match = initResult.userAgent.match(/\/([\d.]+)/);
|
|
535
|
+
if (match) serverVersion = match[1];
|
|
536
|
+
}
|
|
466
537
|
|
|
467
538
|
// Step 2: Send initialized notification
|
|
468
539
|
await this.transport.notify("initialized", {});
|
|
@@ -482,14 +553,27 @@ export class CodexAdapter {
|
|
|
482
553
|
|
|
483
554
|
try {
|
|
484
555
|
if (this.options.threadId) {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
556
|
+
try {
|
|
557
|
+
threadResult = await this.transport.call("thread/resume", {
|
|
558
|
+
threadId: this.options.threadId,
|
|
559
|
+
model: this.options.model,
|
|
560
|
+
cwd: this.options.cwd || "",
|
|
561
|
+
approvalPolicy: this.mapApprovalPolicy(this.currentPermissionMode),
|
|
562
|
+
sandbox: this.mapSandboxPolicy(this.currentPermissionMode),
|
|
563
|
+
}) as { thread: { id: string }; model?: string; model_provider?: string };
|
|
564
|
+
this.threadId = threadResult.thread.id;
|
|
565
|
+
} catch (resumeErr) {
|
|
566
|
+
// Thread not found (e.g. rollout file cleaned up, version upgrade) — fall back to new thread
|
|
567
|
+
console.warn(`[codex-adapter] thread/resume failed: ${resumeErr}, falling back to thread/start`);
|
|
568
|
+
this.options.threadId = undefined;
|
|
569
|
+
threadResult = await this.transport.call("thread/start", {
|
|
570
|
+
model: this.options.model,
|
|
571
|
+
cwd: this.options.cwd || "",
|
|
572
|
+
approvalPolicy: this.mapApprovalPolicy(this.currentPermissionMode),
|
|
573
|
+
sandbox: this.mapSandboxPolicy(this.currentPermissionMode),
|
|
574
|
+
}) as { thread: { id: string }; model?: string; model_provider?: string };
|
|
575
|
+
this.threadId = threadResult.thread.id;
|
|
576
|
+
}
|
|
493
577
|
} else {
|
|
494
578
|
threadResult = await this.transport.call("thread/start", {
|
|
495
579
|
model: this.options.model,
|
|
@@ -727,10 +811,19 @@ export class CodexAdapter {
|
|
|
727
811
|
break; // We already got the thread ID from the RPC response
|
|
728
812
|
|
|
729
813
|
case "thread/status/changed": {
|
|
730
|
-
|
|
731
|
-
|
|
814
|
+
// v0.114+: status is an object { type: "active"|"idle"|"systemError"|"notLoaded", activeFlags?: [] }
|
|
815
|
+
// Legacy: status was a plain string
|
|
816
|
+
const rawStatus = params.status;
|
|
817
|
+
const statusType = typeof rawStatus === "object" && rawStatus !== null
|
|
818
|
+
? (rawStatus as Record<string, unknown>).type as string
|
|
819
|
+
: rawStatus as string;
|
|
820
|
+
|
|
821
|
+
if (statusType === "active" || statusType === "running") {
|
|
732
822
|
this.emit({ type: "status_change", status: "running" });
|
|
733
|
-
} else if (
|
|
823
|
+
} else if (statusType === "idle" || statusType === "completed" || statusType === "notLoaded") {
|
|
824
|
+
this.emit({ type: "status_change", status: "idle" });
|
|
825
|
+
} else if (statusType === "systemError") {
|
|
826
|
+
this.emit({ type: "error", message: "Codex reported a system error" } as BrowserIncomingMessage);
|
|
734
827
|
this.emit({ type: "status_change", status: "idle" });
|
|
735
828
|
}
|
|
736
829
|
// Extract model if reported in status
|
|
@@ -755,14 +848,21 @@ export class CodexAdapter {
|
|
|
755
848
|
// Update turn count
|
|
756
849
|
this.turnCount++;
|
|
757
850
|
|
|
758
|
-
//
|
|
759
|
-
const
|
|
851
|
+
// v0.114+: status is in params.turn.status; legacy: params.status
|
|
852
|
+
const turn = params.turn as { status?: string; error?: { message?: string } } | undefined;
|
|
853
|
+
const status = turn?.status ?? params.status as string ?? "completed";
|
|
854
|
+
// Legacy: params.usage; v0.114+: usage arrives via thread/tokenUsage/updated
|
|
760
855
|
const usage = params.usage as Record<string, number> | undefined;
|
|
761
856
|
if (usage) {
|
|
762
857
|
this.cumulativeInputTokens += usage.inputTokens ?? 0;
|
|
763
858
|
this.cumulativeOutputTokens += usage.outputTokens ?? 0;
|
|
764
859
|
}
|
|
765
860
|
|
|
861
|
+
// Surface turn errors
|
|
862
|
+
if (turn?.error?.message) {
|
|
863
|
+
this.emit({ type: "error", message: turn.error.message } as BrowserIncomingMessage);
|
|
864
|
+
}
|
|
865
|
+
|
|
766
866
|
// Build a synthetic result message
|
|
767
867
|
const result: CLIResultMessage = {
|
|
768
868
|
type: "result",
|
|
@@ -821,6 +921,7 @@ export class CodexAdapter {
|
|
|
821
921
|
// ── Reasoning / thinking ──
|
|
822
922
|
case "item/reasoning/textDelta":
|
|
823
923
|
case "item/reasoning/textSummaryDelta":
|
|
924
|
+
case "item/reasoning/summaryTextDelta":
|
|
824
925
|
this.handleReasoningDelta(params);
|
|
825
926
|
break;
|
|
826
927
|
|
|
@@ -858,7 +959,8 @@ export class CodexAdapter {
|
|
|
858
959
|
}
|
|
859
960
|
|
|
860
961
|
case "codex/event/mcp_startup_complete":
|
|
861
|
-
|
|
962
|
+
case "codex/event/mcp_startup_update":
|
|
963
|
+
// MCP servers loading / finished loading
|
|
862
964
|
break;
|
|
863
965
|
|
|
864
966
|
case "codex/event/user_message":
|
|
@@ -872,12 +974,50 @@ export class CodexAdapter {
|
|
|
872
974
|
break;
|
|
873
975
|
}
|
|
874
976
|
|
|
977
|
+
// v0.114+: model rerouted — update active model
|
|
978
|
+
case "model/rerouted": {
|
|
979
|
+
const toModel = params.toModel as string | undefined;
|
|
980
|
+
if (toModel) {
|
|
981
|
+
this.activeModel = toModel;
|
|
982
|
+
this.emitSessionUpdate({ model: toModel });
|
|
983
|
+
}
|
|
984
|
+
break;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// v0.114+: context compacted via notification (not just item)
|
|
988
|
+
case "thread/compacted":
|
|
989
|
+
this.emitSessionUpdate({ is_compacting: false });
|
|
990
|
+
break;
|
|
991
|
+
|
|
992
|
+
// v0.114+: hooks, plans, diffs, server request resolved — informational
|
|
993
|
+
case "hook/started":
|
|
994
|
+
case "hook/completed":
|
|
995
|
+
case "turn/diff/updated":
|
|
996
|
+
case "turn/plan/updated":
|
|
997
|
+
case "item/plan/delta":
|
|
998
|
+
case "serverRequest/resolved":
|
|
999
|
+
case "deprecationNotice":
|
|
1000
|
+
case "configWarning":
|
|
1001
|
+
case "thread/started":
|
|
1002
|
+
case "thread/closed":
|
|
1003
|
+
case "thread/archived":
|
|
1004
|
+
case "thread/unarchived":
|
|
1005
|
+
case "thread/name/updated":
|
|
1006
|
+
case "skills/changed":
|
|
1007
|
+
case "item/mcpToolCall/progress":
|
|
1008
|
+
// Known notifications — no action needed
|
|
1009
|
+
break;
|
|
1010
|
+
|
|
875
1011
|
default:
|
|
876
1012
|
// Silently ignore known event prefixes, log truly unknown ones
|
|
877
1013
|
if (!method.startsWith("account/")
|
|
878
1014
|
&& !method.startsWith("codex/event/")
|
|
879
1015
|
&& !method.startsWith("rawResponseItem/")
|
|
880
|
-
&& method
|
|
1016
|
+
&& !method.startsWith("fuzzyFileSearch/")
|
|
1017
|
+
&& !method.startsWith("thread/realtime/")
|
|
1018
|
+
&& !method.startsWith("app/")
|
|
1019
|
+
&& !method.startsWith("mcpServer/")
|
|
1020
|
+
&& !method.startsWith("windows")) {
|
|
881
1021
|
console.log(`[codex-adapter] Unhandled notification: ${method}`);
|
|
882
1022
|
}
|
|
883
1023
|
break;
|
|
@@ -918,12 +1058,14 @@ export class CodexAdapter {
|
|
|
918
1058
|
const requestId = randomUUID();
|
|
919
1059
|
this.pendingApprovals.set(requestId, id);
|
|
920
1060
|
|
|
921
|
-
|
|
922
|
-
const
|
|
1061
|
+
// v0.114+: `server`/`tool`/`arguments`; legacy: `serverName`/`toolName`/`args`
|
|
1062
|
+
const serverName = (params.server ?? params.serverName) as string || "";
|
|
1063
|
+
const toolName = (params.tool ?? params.toolName) as string || "";
|
|
1064
|
+
const toolArgs = (params.arguments ?? params.args) as Record<string, unknown> || {};
|
|
923
1065
|
const perm: PermissionRequest = {
|
|
924
1066
|
request_id: requestId,
|
|
925
1067
|
tool_name: `mcp:${serverName}:${toolName}`,
|
|
926
|
-
input:
|
|
1068
|
+
input: toolArgs,
|
|
927
1069
|
description: `MCP tool: ${serverName}/${toolName}`,
|
|
928
1070
|
tool_use_id: (params.itemId as string) || randomUUID(),
|
|
929
1071
|
timestamp: Date.now(),
|
|
@@ -955,10 +1097,77 @@ export class CodexAdapter {
|
|
|
955
1097
|
break;
|
|
956
1098
|
}
|
|
957
1099
|
|
|
1100
|
+
// v0.114+: permissions approval request
|
|
1101
|
+
case "item/permissions/requestApproval": {
|
|
1102
|
+
const requestId = randomUUID();
|
|
1103
|
+
this.pendingApprovals.set(requestId, id);
|
|
1104
|
+
const reason = params.reason as string || "Permission request";
|
|
1105
|
+
const permissions = params.permissions as Record<string, unknown> || {};
|
|
1106
|
+
const perm: PermissionRequest = {
|
|
1107
|
+
request_id: requestId,
|
|
1108
|
+
tool_name: "Permissions",
|
|
1109
|
+
input: permissions,
|
|
1110
|
+
description: reason,
|
|
1111
|
+
tool_use_id: (params.itemId as string) || randomUUID(),
|
|
1112
|
+
timestamp: Date.now(),
|
|
1113
|
+
};
|
|
1114
|
+
this.emit({ type: "permission_request", request: perm });
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// v0.114+: tool requests user input — treat as permission request
|
|
1119
|
+
case "item/tool/requestUserInput": {
|
|
1120
|
+
const requestId = randomUUID();
|
|
1121
|
+
this.pendingApprovals.set(requestId, id);
|
|
1122
|
+
const questions = params.questions as Array<{ text?: string }> | undefined;
|
|
1123
|
+
const desc = questions?.map((q) => q.text).filter(Boolean).join("; ") || "Tool requests input";
|
|
1124
|
+
const perm: PermissionRequest = {
|
|
1125
|
+
request_id: requestId,
|
|
1126
|
+
tool_name: "UserInput",
|
|
1127
|
+
input: { questions: questions || [] },
|
|
1128
|
+
description: desc,
|
|
1129
|
+
tool_use_id: (params.itemId as string) || randomUUID(),
|
|
1130
|
+
timestamp: Date.now(),
|
|
1131
|
+
};
|
|
1132
|
+
this.emit({ type: "permission_request", request: perm });
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// v0.114+: MCP server elicitation
|
|
1137
|
+
case "mcpServer/elicitation/request": {
|
|
1138
|
+
const requestId = randomUUID();
|
|
1139
|
+
this.pendingApprovals.set(requestId, id);
|
|
1140
|
+
const serverName = params.serverName as string || "";
|
|
1141
|
+
const message = params.message as string || "MCP server elicitation";
|
|
1142
|
+
const perm: PermissionRequest = {
|
|
1143
|
+
request_id: requestId,
|
|
1144
|
+
tool_name: `mcp:${serverName}:elicitation`,
|
|
1145
|
+
input: params,
|
|
1146
|
+
description: message,
|
|
1147
|
+
tool_use_id: randomUUID(),
|
|
1148
|
+
timestamp: Date.now(),
|
|
1149
|
+
};
|
|
1150
|
+
this.emit({ type: "permission_request", request: perm });
|
|
1151
|
+
break;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// v0.114+: dynamic tool call — execute client-side
|
|
1155
|
+
case "item/tool/call": {
|
|
1156
|
+
// We don't support client-side dynamic tools — decline gracefully
|
|
1157
|
+
console.log(`[codex-adapter] Dynamic tool call not supported: ${params.tool}`);
|
|
1158
|
+
this.transport.respond(id, { error: "Dynamic tools not supported by this client" }).catch(() => {});
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Account token refresh — respond silently
|
|
1163
|
+
case "account/chatgptAuthTokens/refresh":
|
|
1164
|
+
this.transport.respond(id, {}).catch(() => {});
|
|
1165
|
+
break;
|
|
1166
|
+
|
|
958
1167
|
default:
|
|
959
|
-
// Unknown request —
|
|
960
|
-
console.warn(`[codex-adapter] Unknown request method: ${method},
|
|
961
|
-
this.transport.respond(id, { decision: "
|
|
1168
|
+
// Unknown request — log and reject to avoid silently approving dangerous operations
|
|
1169
|
+
console.warn(`[codex-adapter] Unknown request method: ${method}, rejecting`);
|
|
1170
|
+
this.transport.respond(id, { decision: "decline" }).catch(() => {});
|
|
962
1171
|
break;
|
|
963
1172
|
}
|
|
964
1173
|
}
|
|
@@ -1018,11 +1227,11 @@ export class CodexAdapter {
|
|
|
1018
1227
|
const toolUseId = item.id;
|
|
1019
1228
|
if (!this.emittedToolUseIds.has(toolUseId)) {
|
|
1020
1229
|
this.emittedToolUseIds.add(toolUseId);
|
|
1021
|
-
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
});
|
|
1230
|
+
// v0.114+: `server`/`tool`/`arguments`; legacy: `serverName`/`toolName`/`args`
|
|
1231
|
+
const serverName = (item.server ?? item.serverName) as string || "";
|
|
1232
|
+
const toolName = (item.tool ?? item.toolName) as string || "";
|
|
1233
|
+
const toolArgs = (item.arguments ?? item.args) as Record<string, unknown> || {};
|
|
1234
|
+
this.emitToolUse(toolUseId, `mcp:${serverName}:${toolName}`, { ...toolArgs });
|
|
1026
1235
|
}
|
|
1027
1236
|
break;
|
|
1028
1237
|
}
|
|
@@ -1069,7 +1278,8 @@ export class CodexAdapter {
|
|
|
1069
1278
|
|
|
1070
1279
|
// Emit tool_result with output
|
|
1071
1280
|
const exitCode = item.exitCode as number | undefined;
|
|
1072
|
-
|
|
1281
|
+
// v0.114+: `aggregatedOutput`; legacy: `output`
|
|
1282
|
+
const output = (item.aggregatedOutput ?? item.output) as string | undefined;
|
|
1073
1283
|
const isError = item.status === "failed" || (exitCode !== undefined && exitCode !== 0);
|
|
1074
1284
|
const resultText = output
|
|
1075
1285
|
? output.substring(0, 2000) + (output.length > 2000 ? "\n…truncated" : "")
|
|
@@ -1128,12 +1338,17 @@ export class CodexAdapter {
|
|
|
1128
1338
|
const toolUseId = item.id;
|
|
1129
1339
|
if (!this.emittedToolUseIds.has(toolUseId)) {
|
|
1130
1340
|
this.emittedToolUseIds.add(toolUseId);
|
|
1131
|
-
|
|
1132
|
-
const
|
|
1341
|
+
// v0.114+: `server`/`tool`; legacy: `serverName`/`toolName`
|
|
1342
|
+
const serverName = (item.server ?? item.serverName) as string || "";
|
|
1343
|
+
const toolName = (item.tool ?? item.toolName) as string || "";
|
|
1133
1344
|
this.emitToolUse(toolUseId, `mcp:${serverName}:${toolName}`, {});
|
|
1134
1345
|
}
|
|
1135
1346
|
const isError = item.status === "failed";
|
|
1136
|
-
|
|
1347
|
+
// v0.114+: error is { message: string }; result is { content: [...] }
|
|
1348
|
+
const errorObj = item.error as { message?: string } | string | undefined;
|
|
1349
|
+
const errorStr = typeof errorObj === "string" ? errorObj : errorObj?.message;
|
|
1350
|
+
const resultObj = item.result as { content?: unknown[] } | undefined;
|
|
1351
|
+
const output = item.output as string || errorStr || (resultObj?.content ? JSON.stringify(resultObj.content) : undefined) || "MCP call completed";
|
|
1137
1352
|
this.emitToolResult(toolUseId, typeof output === "string" ? output : JSON.stringify(output), isError);
|
|
1138
1353
|
break;
|
|
1139
1354
|
}
|
|
@@ -1226,11 +1441,39 @@ export class CodexAdapter {
|
|
|
1226
1441
|
}
|
|
1227
1442
|
|
|
1228
1443
|
private handleTokenUsageUpdated(params: Record<string, unknown>): void {
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
const
|
|
1232
|
-
|
|
1233
|
-
|
|
1444
|
+
// v0.114+: params.tokenUsage = { total: { totalTokens, inputTokens, ... }, last: {...}, modelContextWindow }
|
|
1445
|
+
// Legacy: flat params.inputTokens, params.outputTokens, etc.
|
|
1446
|
+
const tokenUsage = params.tokenUsage as {
|
|
1447
|
+
total?: { totalTokens?: number; inputTokens?: number; outputTokens?: number; cachedInputTokens?: number; reasoningOutputTokens?: number };
|
|
1448
|
+
last?: { totalTokens?: number; inputTokens?: number; outputTokens?: number };
|
|
1449
|
+
modelContextWindow?: number | null;
|
|
1450
|
+
} | undefined;
|
|
1451
|
+
|
|
1452
|
+
let inputTokens: number;
|
|
1453
|
+
let outputTokens: number;
|
|
1454
|
+
let modelContextWindow: number;
|
|
1455
|
+
let costUsd: number;
|
|
1456
|
+
let model: string | undefined;
|
|
1457
|
+
|
|
1458
|
+
if (tokenUsage?.total) {
|
|
1459
|
+
// v0.114+ format
|
|
1460
|
+
inputTokens = tokenUsage.total.inputTokens ?? 0;
|
|
1461
|
+
outputTokens = tokenUsage.total.outputTokens ?? 0;
|
|
1462
|
+
modelContextWindow = tokenUsage.modelContextWindow ?? 128_000;
|
|
1463
|
+
costUsd = (params.costUsd as number) || 0;
|
|
1464
|
+
model = params.model as string | undefined;
|
|
1465
|
+
|
|
1466
|
+
// Update cumulative counters from total
|
|
1467
|
+
this.cumulativeInputTokens = inputTokens;
|
|
1468
|
+
this.cumulativeOutputTokens = outputTokens;
|
|
1469
|
+
} else {
|
|
1470
|
+
// Legacy flat format
|
|
1471
|
+
inputTokens = (params.inputTokens as number) || 0;
|
|
1472
|
+
outputTokens = (params.outputTokens as number) || 0;
|
|
1473
|
+
modelContextWindow = (params.modelContextWindow as number) || 128_000;
|
|
1474
|
+
costUsd = (params.costUsd as number) || 0;
|
|
1475
|
+
model = params.model as string | undefined;
|
|
1476
|
+
}
|
|
1234
1477
|
|
|
1235
1478
|
// Update cumulative cost if provided
|
|
1236
1479
|
if (costUsd > 0) {
|
|
@@ -1254,7 +1497,8 @@ export class CodexAdapter {
|
|
|
1254
1497
|
// ── Helper methods ──────────────────────────────────────────────────────
|
|
1255
1498
|
|
|
1256
1499
|
private emit(msg: BrowserIncomingMessage): void {
|
|
1257
|
-
this.browserMessageCb
|
|
1500
|
+
if (!this.browserMessageCb) return;
|
|
1501
|
+
this.browserMessageCb(msg);
|
|
1258
1502
|
}
|
|
1259
1503
|
|
|
1260
1504
|
private emitSessionUpdate(fields: Partial<SessionState>): void {
|