wind-agent-cli 1.37.2 → 1.37.3
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/LICENSE +718 -718
- package/README.md +1 -1
- package/dist/api-docs.html +901 -901
- package/dist/assets/{CodeEditor-B2Wf7Fg0.js → CodeEditor-BqmxpZ_D.js} +1 -1
- package/dist/assets/{FileTree-N2IkkHlv.js → FileTree-DLE7Ioai.js} +1 -1
- package/dist/assets/{GitPanel-DZCeuFZ2.js → GitPanel-f8RMRa8D.js} +1 -1
- package/dist/assets/{MarkdownPreview-C0tqYnVu.js → MarkdownPreview-93h_1tJa.js} +1 -1
- package/dist/assets/{PluginTabContent-Bnuf-fb8.js → PluginTabContent-DU1jjkXP.js} +1 -1
- package/dist/assets/{Shell-BfFnMZ-f.js → Shell-CN_2MRIM.js} +1 -1
- package/dist/assets/{StandaloneShell-QmyYwNAl.js → StandaloneShell-D0avS8UE.js} +1 -1
- package/dist/assets/{TaskMasterPanel-fMsmvN9L.js → TaskMasterPanel-Cpf1Q5Ph.js} +3 -3
- package/dist/assets/{abnfDiagram-N423BO3Z-DjPU7HLs.js → abnfDiagram-N423BO3Z-p3aJIm4T.js} +1 -1
- package/dist/assets/{arc-Pdq6Tvr3.js → arc-CTeud6Fl.js} +1 -1
- package/dist/assets/{architectureDiagram-T3A2C74G-D0JnT0Vh.js → architectureDiagram-T3A2C74G-CG7kRCJU.js} +1 -1
- package/dist/assets/{blockDiagram-VBNYF7ZC-DpaEf3mv.js → blockDiagram-VBNYF7ZC-oSjQ1SgE.js} +1 -1
- package/dist/assets/{c4Diagram-5PPSVZJV-MpqlPGFi.js → c4Diagram-5PPSVZJV-RkzF0DJV.js} +1 -1
- package/dist/assets/channel-BfQbarn9.js +1 -0
- package/dist/assets/{chunk-2GRJ4B5K-DoSdNo8u.js → chunk-2GRJ4B5K-B07uZ07O.js} +1 -1
- package/dist/assets/{chunk-2Q5K7J3B-CE7I3uMn.js → chunk-2Q5K7J3B-DNSfO7xS.js} +1 -1
- package/dist/assets/{chunk-5RXB4S5H-B4UQ8snD.js → chunk-5RXB4S5H-BTeYcgyb.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-B55XgqHZ.js → chunk-5VM5RSS4-B7vZV0zC.js} +1 -1
- package/dist/assets/{chunk-6Q2QTUOP-BS1KpefW.js → chunk-6Q2QTUOP-DuS_xUGG.js} +1 -1
- package/dist/assets/{chunk-GF5L2VYU-DkEPedJP.js → chunk-GF5L2VYU-CKLZ3iVD.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DOtkNhMm.js → chunk-JWPE2WC7-DD2hcXvs.js} +1 -1
- package/dist/assets/{chunk-KBJHAD2P-BXLjviqM.js → chunk-KBJHAD2P-Cz-l38qp.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-C50fjNNz.js → chunk-RYQCIY6F-DGFIU1YM.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-QBYqCkb7.js → chunk-XXDRQBXY-CmfzoYFl.js} +1 -1
- package/dist/assets/classDiagram-JCYQIIEL-Bpfe3kq7.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-Bpfe3kq7.js +1 -0
- package/dist/assets/{code-xml-VK1YfShP.js → code-xml-CLpHzEOP.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-DrVTo1Nh.js → cose-bilkent-JH36ORCC-DlFcLXCB.js} +1 -1
- package/dist/assets/{cynefin-VYW2F7L2-DnJa_OlQ.js → cynefin-VYW2F7L2-P1HecReO.js} +1 -1
- package/dist/assets/{cynefinDiagram-MW4NZA55-CCxBjw9E.js → cynefinDiagram-MW4NZA55-COgduz5P.js} +1 -1
- package/dist/assets/{dagre-VZM6K2ZE-C_GvE4lu.js → dagre-VZM6K2ZE-Bbb-siGp.js} +1 -1
- package/dist/assets/{diagram-7IWD3JNH-pFzs9HEu.js → diagram-7IWD3JNH-9CWGFMYR.js} +1 -1
- package/dist/assets/{diagram-B4RE2ZJO-CcehQC7x.js → diagram-B4RE2ZJO-TWK59T6V.js} +1 -1
- package/dist/assets/{diagram-LBJQPF4R-hfKcrrxe.js → diagram-LBJQPF4R-ErMC9yaa.js} +1 -1
- package/dist/assets/{diagram-Q27KOJAE-C1semJfK.js → diagram-Q27KOJAE-Dt_-C7hV.js} +1 -1
- package/dist/assets/{diagram-UB23O5K3-57hvXQXO.js → diagram-UB23O5K3-BJ1DnRAF.js} +1 -1
- package/dist/assets/{ebnfDiagram-BXEA7PRR-9KIAlAVw.js → ebnfDiagram-BXEA7PRR-CE1YaQ9s.js} +1 -1
- package/dist/assets/{erDiagram-JOGREHBK-B7OzJ767.js → erDiagram-JOGREHBK-Bqk7N9aY.js} +1 -1
- package/dist/assets/{flowDiagram-UKHOOZJN-CJV8fQPR.js → flowDiagram-UKHOOZJN-DEr2II5V.js} +1 -1
- package/dist/assets/{ganttDiagram-PKOTCBZU-CbMNWWQu.js → ganttDiagram-PKOTCBZU-CWYAUeYN.js} +1 -1
- package/dist/assets/{gitGraphDiagram-DS77QQ5N-I_vZ5waO.js → gitGraphDiagram-DS77QQ5N-tdEcEIM6.js} +1 -1
- package/dist/assets/{index-ZHwIojzi.js → index-B-sTfSZq.js} +1 -1
- package/dist/assets/{index-GFhXZyl2.js → index-BSe3UiFA.js} +1 -1
- package/dist/assets/{index-DFnVoqpx.js → index-B_FDMWar.js} +5 -5
- package/dist/assets/{index-DlSeAkw_.js → index-CWJtg2pR.js} +1 -1
- package/dist/assets/{infoDiagram-6WML65LV-Dh20Tsbq.js → infoDiagram-6WML65LV-A3IqRUnO.js} +1 -1
- package/dist/assets/{ishikawaDiagram-WSZJBQD7-DH2Sm3ko.js → ishikawaDiagram-WSZJBQD7-B2uL6bkK.js} +1 -1
- package/dist/assets/{journeyDiagram-NVQOT4AX-BNFQqlVu.js → journeyDiagram-NVQOT4AX-D9JB-hRW.js} +1 -1
- package/dist/assets/{kanban-definition-27J2QSJJ-B3iL0ztJ.js → kanban-definition-27J2QSJJ-DmlgiV7K.js} +1 -1
- package/dist/assets/{linear-Cwe9dEKA.js → linear-Ct_eoJQk.js} +1 -1
- package/dist/assets/{mermaid.core-BYRQSshi.js → mermaid.core-CQ698HNn.js} +5 -5
- package/dist/assets/{mindmap-definition-FAOFIHXS-BgfBYMzr.js → mindmap-definition-FAOFIHXS-DGtMgQhf.js} +1 -1
- package/dist/assets/{pegDiagram-VL7TDLO6-CNYpECr8.js → pegDiagram-VL7TDLO6-CO6vP4n9.js} +1 -1
- package/dist/assets/{pieDiagram-7S7Q4E2Y-DeSrxc4I.js → pieDiagram-7S7Q4E2Y-DMkt_Zfr.js} +1 -1
- package/dist/assets/{quadrantDiagram-CIZ2JOQS-BXVVQsa6.js → quadrantDiagram-CIZ2JOQS-DgNxRz6t.js} +1 -1
- package/dist/assets/{railroadDiagram-AXF67PYL-JTroIW8F.js → railroadDiagram-AXF67PYL-DKz9UqA9.js} +1 -1
- package/dist/assets/{requirementDiagram-LRYGKXZP-C1Z_inLz.js → requirementDiagram-LRYGKXZP-L84XyGgV.js} +1 -1
- package/dist/assets/{sankeyDiagram-W5VNT64P-DCJgKFUk.js → sankeyDiagram-W5VNT64P-CQqWbJ3j.js} +1 -1
- package/dist/assets/{sequenceDiagram-SI44F4Z6-D8Wrx5Vc.js → sequenceDiagram-SI44F4Z6--tnzS2tb.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-CXqD68vD.js → sizeCapture-X5ZJPWSS-DujwEOtm.js} +1 -1
- package/dist/assets/{stateDiagram-OKZ733FA-CF0Ekqjo.js → stateDiagram-OKZ733FA-BK3TcZY2.js} +1 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-_medyomK.js +1 -0
- package/dist/assets/{swimlanes-SLNWSIFB-DVIax_k4.js → swimlanes-SLNWSIFB-naX-Uv1B.js} +2 -2
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-DstIv3TC.js +8 -0
- package/dist/assets/{timeline-definition-Z64GVDOM-DSqk3ApG.js → timeline-definition-Z64GVDOM-C-vP_ddU.js} +1 -1
- package/dist/assets/{vennDiagram-T6HMQDX7-DNbTqyG5.js → vennDiagram-T6HMQDX7-DN-I7o-7.js} +1 -1
- package/dist/assets/{wardleyDiagram-T6FBY63Y-BNCpsFZV.js → wardleyDiagram-T6FBY63Y-Db9dsaXv.js} +1 -1
- package/dist/assets/{xychartDiagram-ELKLHX3M-XsVZphHr.js → xychartDiagram-ELKLHX3M-DE6OxW_p.js} +1 -1
- package/dist/clear-cache.html +85 -85
- package/dist/convert-icons.md +52 -52
- package/dist/favicon.svg +8 -8
- package/dist/generate-icons.js +48 -48
- package/dist/icons/codex-white.svg +3 -3
- package/dist/icons/codex.svg +3 -3
- package/dist/icons/cursor-white.svg +11 -11
- package/dist/icons/icon-128x128.svg +11 -11
- package/dist/icons/icon-144x144.svg +11 -11
- package/dist/icons/icon-152x152.svg +11 -11
- package/dist/icons/icon-192x192.svg +11 -11
- package/dist/icons/icon-384x384.svg +11 -11
- package/dist/icons/icon-512x512.svg +11 -11
- package/dist/icons/icon-72x72.svg +11 -11
- package/dist/icons/icon-96x96.svg +11 -11
- package/dist/icons/icon-template.svg +11 -11
- package/dist/index.html +56 -56
- package/dist/logo.svg +17 -17
- package/dist/manifest.json +60 -60
- package/dist/sw.js +124 -124
- package/dist-server/server/modules/cli/cli.service.js +36 -36
- package/dist-server/server/modules/cli/sandbox.service.js +38 -38
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +74 -74
- package/dist-server/server/modules/commands/commands.routes.js +24 -24
- package/dist-server/server/modules/database/migrations.js +190 -190
- package/dist-server/server/modules/database/repositories/api-keys.js +3 -3
- package/dist-server/server/modules/database/repositories/github-tokens.js +2 -2
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +28 -28
- package/dist-server/server/modules/database/repositories/notification-preferences.js +4 -4
- package/dist-server/server/modules/database/repositories/projects.db.js +61 -61
- package/dist-server/server/modules/database/repositories/provider-models.js +35 -35
- package/dist-server/server/modules/database/repositories/push-subscriptions.js +5 -5
- package/dist-server/server/modules/database/repositories/scan-state.db.js +5 -5
- package/dist-server/server/modules/database/repositories/sessions.db.js +92 -92
- package/dist-server/server/modules/database/schema.js +177 -177
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +6 -6
- package/dist-server/server/modules/git/git.routes.js +17 -17
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +12 -12
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +9 -9
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +17 -17
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +25 -25
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +27 -27
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js +9 -9
- package/dist-server/server/modules/providers/tests/mcp.test.js +3 -3
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +96 -96
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +18 -18
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js +419 -419
- package/electron/cloud.js +260 -260
- package/electron/desktopNotifications.js +378 -378
- package/electron/desktopWindow.js +766 -766
- package/electron/launcher/index.html +14 -14
- package/electron/launcher/launcher.css +801 -801
- package/electron/launcher/launcher.js +687 -687
- package/electron/localServer.js +549 -549
- package/electron/main.js +944 -944
- package/electron/preload.cjs +60 -60
- package/electron/scripts/generate-macos-icon.js +62 -62
- package/electron/serverInstaller.js +277 -277
- package/electron/tabs.js +87 -87
- package/electron/viewHost.js +331 -331
- package/package.json +250 -247
- package/public/api-docs.html +901 -901
- package/scripts/fix-node-pty.js +67 -67
- package/scripts/promote-dist-server.mjs +50 -50
- package/scripts/release/build-server-bundle.js +176 -176
- package/scripts/release/prepare-desktop-app.js +152 -152
- package/server/index.ts +399 -399
- package/server/load-env.ts +47 -47
- package/server/modules/agent/agent.module.ts +52 -52
- package/server/modules/agent/agent.routes.ts +1287 -1287
- package/server/modules/agent/index.ts +2 -2
- package/server/modules/agent/tests/agent.routes.test.ts +205 -205
- package/server/modules/assets/assets.routes.ts +150 -150
- package/server/modules/assets/index.ts +3 -3
- package/server/modules/assets/services/image-assets.service.ts +127 -127
- package/server/modules/assets/tests/image-assets.service.test.ts +74 -74
- package/server/modules/auth/auth.middleware.ts +163 -163
- package/server/modules/auth/auth.module.ts +38 -38
- package/server/modules/auth/auth.routes.ts +57 -57
- package/server/modules/auth/auth.service.ts +155 -155
- package/server/modules/auth/index.ts +9 -9
- package/server/modules/auth/tests/auth.service.test.ts +95 -95
- package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -120
- package/server/modules/browser-use/browser-use-mcp.ts +386 -386
- package/server/modules/browser-use/browser-use-runtime.ts +10 -10
- package/server/modules/browser-use/browser-use.routes.ts +96 -96
- package/server/modules/browser-use/browser-use.service.ts +836 -836
- package/server/modules/browser-use/index.ts +11 -11
- package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -10
- package/server/modules/cli/cli.module.ts +93 -93
- package/server/modules/cli/cli.service.ts +248 -248
- package/server/modules/cli/cli.ts +19 -19
- package/server/modules/cli/index.ts +2 -2
- package/server/modules/cli/sandbox.service.ts +398 -398
- package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -186
- package/server/modules/cli/tests/cli.service.test.ts +86 -86
- package/server/modules/cli/tests/sandbox.service.test.ts +58 -58
- package/server/modules/commands/commands.module.ts +22 -22
- package/server/modules/commands/commands.routes.ts +586 -586
- package/server/modules/commands/index.ts +2 -2
- package/server/modules/commands/tests/commands.test.ts +109 -109
- package/server/modules/database/connection.ts +143 -143
- package/server/modules/database/index.ts +17 -17
- package/server/modules/database/init-db.ts +17 -17
- package/server/modules/database/migrations.ts +515 -515
- package/server/modules/database/repositories/api-keys.ts +119 -119
- package/server/modules/database/repositories/app-config.ts +53 -53
- package/server/modules/database/repositories/credentials.ts +106 -106
- package/server/modules/database/repositories/github-tokens.ts +100 -100
- package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -153
- package/server/modules/database/repositories/notification-preferences.ts +117 -117
- package/server/modules/database/repositories/projects.db.ts +196 -196
- package/server/modules/database/repositories/provider-models.ts +168 -168
- package/server/modules/database/repositories/push-subscriptions.ts +80 -80
- package/server/modules/database/repositories/scan-state.db.ts +42 -42
- package/server/modules/database/repositories/sessions.db.ts +524 -524
- package/server/modules/database/repositories/users.ts +140 -140
- package/server/modules/database/repositories/vapid-keys.ts +57 -57
- package/server/modules/database/schema.ts +210 -210
- package/server/modules/database/tests/projects.db.integration.test.ts +72 -72
- package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -125
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +109 -109
- package/server/modules/database/tests/sessions.db.integration.test.ts +116 -116
- package/server/modules/file-tree/file-tree.module.ts +116 -116
- package/server/modules/file-tree/file-tree.routes.ts +258 -258
- package/server/modules/file-tree/file-tree.service.ts +675 -675
- package/server/modules/file-tree/index.ts +2 -2
- package/server/modules/file-tree/tests/file-tree.routes.test.ts +157 -157
- package/server/modules/file-tree/tests/file-tree.service.test.ts +371 -371
- package/server/modules/git/git-branch.service.ts +43 -43
- package/server/modules/git/git-parsing.service.ts +66 -66
- package/server/modules/git/git.module.ts +22 -22
- package/server/modules/git/git.routes.ts +1596 -1596
- package/server/modules/git/index.ts +2 -2
- package/server/modules/git/tests/git-branch.service.test.ts +60 -60
- package/server/modules/git/tests/git-init.routes.test.ts +132 -132
- package/server/modules/git/tests/git.test.ts +106 -106
- package/server/modules/notifications/index.ts +24 -24
- package/server/modules/notifications/notifications.routes.ts +127 -127
- package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -124
- package/server/modules/notifications/services/notification-orchestrator.service.js +310 -310
- package/server/modules/notifications/tests/notification-orchestrator.integration.test.ts +55 -55
- package/server/modules/notifications/vapid-keys.service.ts +38 -38
- package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -109
- package/server/modules/plugins/index.ts +9 -9
- package/server/modules/plugins/plugin-process.service.ts +217 -217
- package/server/modules/plugins/plugin-registry.service.ts +459 -459
- package/server/modules/plugins/plugins.module.ts +33 -33
- package/server/modules/plugins/plugins.routes.ts +66 -66
- package/server/modules/plugins/plugins.service.ts +147 -147
- package/server/modules/plugins/tests/plugins.service.test.ts +32 -32
- package/server/modules/projects/index.ts +11 -11
- package/server/modules/projects/projects.routes.ts +273 -273
- package/server/modules/projects/services/project-clone.service.ts +323 -323
- package/server/modules/projects/services/project-delete.service.ts +90 -90
- package/server/modules/projects/services/project-management.service.ts +144 -144
- package/server/modules/projects/services/project-star.service.ts +78 -78
- package/server/modules/projects/services/projects-has-taskmaster.service.ts +248 -248
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +314 -314
- package/server/modules/projects/tests/project-clone.service.test.ts +183 -183
- package/server/modules/projects/tests/project-management.service.test.ts +117 -117
- package/server/modules/projects/tests/project-star.service.test.ts +123 -123
- package/server/modules/projects/tests/projects-has-taskmaster.service.test.ts +105 -105
- package/server/modules/providers/README.md +380 -380
- package/server/modules/providers/index.ts +10 -10
- package/server/modules/providers/list/claude/claude-auth.provider.ts +160 -160
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
- package/server/modules/providers/list/claude/claude-models.provider.ts +294 -294
- package/server/modules/providers/list/claude/claude-runtime.provider.js +1068 -1068
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +203 -203
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +684 -684
- package/server/modules/providers/list/claude/claude-skills.provider.ts +265 -265
- package/server/modules/providers/list/claude/claude.provider.ts +30 -30
- package/server/modules/providers/list/codex/codex-auth.provider.ts +100 -100
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
- package/server/modules/providers/list/codex/codex-models.provider.ts +123 -123
- package/server/modules/providers/list/codex/codex-runtime.provider.js +533 -533
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +203 -203
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +938 -938
- package/server/modules/providers/list/codex/codex-skills.provider.ts +73 -73
- package/server/modules/providers/list/codex/codex.provider.ts +28 -28
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -143
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +119 -119
- package/server/modules/providers/list/cursor/cursor-runtime.provider.js +385 -385
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +159 -159
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +654 -654
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +39 -39
- package/server/modules/providers/list/cursor/cursor.provider.ts +30 -30
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +110 -110
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -228
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +203 -203
- package/server/modules/providers/list/opencode/opencode-runtime.provider.js +434 -434
- package/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +261 -261
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +179 -179
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +500 -500
- package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -78
- package/server/modules/providers/list/opencode/opencode.provider.ts +30 -30
- package/server/modules/providers/provider.registry.ts +36 -36
- package/server/modules/providers/provider.routes.ts +877 -877
- package/server/modules/providers/services/mcp.service.ts +109 -109
- package/server/modules/providers/services/provider-auth.service.ts +26 -26
- package/server/modules/providers/services/provider-capabilities.service.ts +97 -97
- package/server/modules/providers/services/provider-models.service.ts +401 -401
- package/server/modules/providers/services/provider-runtime.service.ts +108 -108
- package/server/modules/providers/services/provider-token-usage.service.ts +357 -357
- package/server/modules/providers/services/session-conversations-search.service.ts +1321 -1321
- package/server/modules/providers/services/session-synchronizer.service.ts +74 -74
- package/server/modules/providers/services/sessions-watcher.service.ts +247 -247
- package/server/modules/providers/services/sessions.service.ts +471 -471
- package/server/modules/providers/services/skills.service.ts +39 -39
- package/server/modules/providers/shared/base/abstract.provider.ts +32 -32
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
- package/server/modules/providers/shared/skills/skills.provider.ts +287 -287
- package/server/modules/providers/tests/claude-auth.test.ts +150 -150
- package/server/modules/providers/tests/claude-sessions.test.ts +61 -61
- package/server/modules/providers/tests/codex-sessions.test.ts +231 -231
- package/server/modules/providers/tests/mcp.test.ts +349 -349
- package/server/modules/providers/tests/opencode-models.test.ts +41 -41
- package/server/modules/providers/tests/opencode-sessions.test.ts +522 -522
- package/server/modules/providers/tests/provider-attachment-history.test.ts +247 -247
- package/server/modules/providers/tests/provider-models.service.test.ts +353 -353
- package/server/modules/providers/tests/provider-runtime.service.test.ts +138 -138
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +186 -186
- package/server/modules/providers/tests/provider.routes.test.ts +244 -244
- package/server/modules/providers/tests/sessions-details.test.ts +73 -73
- package/server/modules/providers/tests/sessions.service.test.ts +127 -127
- package/server/modules/providers/tests/skills.test.ts +692 -692
- package/server/modules/settings/index.ts +2 -2
- package/server/modules/settings/settings.module.ts +50 -50
- package/server/modules/settings/settings.routes.ts +51 -51
- package/server/modules/settings/settings.service.ts +186 -186
- package/server/modules/settings/tests/settings.service.test.ts +54 -54
- package/server/modules/system/index.ts +2 -2
- package/server/modules/system/system.module.ts +62 -62
- package/server/modules/system/system.routes.ts +21 -21
- package/server/modules/system/system.service.ts +76 -76
- package/server/modules/system/tests/system.service.test.ts +118 -118
- package/server/modules/taskmaster/index.ts +2 -2
- package/server/modules/taskmaster/taskmaster.module.ts +24 -24
- package/server/modules/taskmaster/taskmaster.routes.ts +1482 -1482
- package/server/modules/taskmaster/taskmaster.service.ts +132 -132
- package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +144 -144
- package/server/modules/taskmaster/tests/taskmaster.service.test.ts +97 -97
- package/server/modules/user/index.ts +2 -2
- package/server/modules/user/tests/user.service.test.ts +61 -61
- package/server/modules/user/user.module.ts +58 -58
- package/server/modules/user/user.routes.ts +50 -50
- package/server/modules/user/user.service.ts +85 -85
- package/server/modules/voice/index.ts +2 -2
- package/server/modules/voice/tests/voice.service.test.ts +98 -98
- package/server/modules/voice/voice.module.ts +47 -47
- package/server/modules/voice/voice.routes.ts +113 -113
- package/server/modules/voice/voice.service.ts +192 -192
- package/server/modules/websocket/README.md +273 -273
- package/server/modules/websocket/index.ts +3 -3
- package/server/modules/websocket/services/chat-run-registry.service.ts +343 -343
- package/server/modules/websocket/services/chat-session-writer.service.ts +145 -145
- package/server/modules/websocket/services/chat-websocket.service.ts +425 -425
- package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +65 -65
- package/server/modules/websocket/services/shell-websocket.service.ts +502 -502
- package/server/modules/websocket/services/websocket-auth.service.ts +59 -59
- package/server/modules/websocket/services/websocket-server.service.ts +124 -124
- package/server/modules/websocket/services/websocket-state.service.ts +16 -16
- package/server/modules/websocket/services/websocket-writer.service.ts +38 -38
- package/server/modules/websocket/tests/chat-attachment-filter.test.ts +59 -59
- package/server/modules/websocket/tests/chat-run-registry.test.ts +282 -282
- package/server/modules/websocket/tests/shell-websocket.service.test.ts +100 -100
- package/server/modules/websocket/tests/websocket-heartbeat.service.test.ts +78 -78
- package/server/modules/worktrees/index.ts +2 -2
- package/server/modules/worktrees/services/worktree-create-and-open.service.ts +53 -53
- package/server/modules/worktrees/services/worktree-create.service.ts +104 -104
- package/server/modules/worktrees/services/worktree-git.service.ts +207 -207
- package/server/modules/worktrees/services/worktree-list.service.ts +143 -143
- package/server/modules/worktrees/services/worktree-merge.service.ts +157 -157
- package/server/modules/worktrees/services/worktree-open.service.ts +85 -85
- package/server/modules/worktrees/services/worktree-remove.service.ts +88 -88
- package/server/modules/worktrees/tests/worktree-create-and-open.service.test.ts +68 -68
- package/server/modules/worktrees/tests/worktree-create.service.test.ts +142 -142
- package/server/modules/worktrees/tests/worktree-git.service.test.ts +84 -84
- package/server/modules/worktrees/tests/worktree-list.service.test.ts +45 -45
- package/server/modules/worktrees/tests/worktree-merge.service.test.ts +213 -213
- package/server/modules/worktrees/tests/worktree-open.service.test.ts +122 -122
- package/server/modules/worktrees/tests/worktree-remove.service.test.ts +227 -227
- package/server/modules/worktrees/tests/worktrees.routes.test.ts +199 -199
- package/server/modules/worktrees/worktrees.module.ts +113 -113
- package/server/modules/worktrees/worktrees.routes.ts +116 -116
- package/server/shared/claude-cli-path.ts +139 -139
- package/server/shared/frontmatter.ts +18 -18
- package/server/shared/image-attachments.ts +435 -435
- package/server/shared/interfaces.ts +179 -179
- package/server/shared/tests/claude-cli-path.test.ts +61 -61
- package/server/shared/tests/image-attachments.test.ts +353 -353
- package/server/shared/tests/slice-tail-page.test.ts +42 -42
- package/server/shared/types.ts +1226 -1226
- package/server/shared/utils.ts +1161 -1161
- package/server/tsconfig.json +40 -40
- package/shared/networkHosts.js +22 -22
- package/dist/assets/channel-Bm0ZLE4z.js +0 -1
- package/dist/assets/classDiagram-JCYQIIEL-DqEOcNxw.js +0 -1
- package/dist/assets/classDiagram-v2-OCEON4UE-DqEOcNxw.js +0 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-DARLpEFJ.js +0 -1
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-CrgDgaY8.js +0 -8
|
@@ -1,533 +1,533 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenAI Codex SDK Integration
|
|
3
|
-
* =============================
|
|
4
|
-
*
|
|
5
|
-
* This module provides integration with the OpenAI Codex SDK for non-interactive
|
|
6
|
-
* chat sessions. It mirrors the Claude runtime adapter for consistency.
|
|
7
|
-
*
|
|
8
|
-
* ## Usage
|
|
9
|
-
*
|
|
10
|
-
* - codexRuntime.run(command, options, writer, context) - Execute a streamed prompt
|
|
11
|
-
* - abortCodexSession(sessionId) - Cancel an active session
|
|
12
|
-
* - isCodexSessionActive(sessionId) - Check if a session is running
|
|
13
|
-
* - getActiveCodexSessions() - List all active sessions
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { Codex } from '@openai/codex-sdk';
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
appendFilesInputTag,
|
|
20
|
-
buildCodexInputItems,
|
|
21
|
-
normalizeImageDescriptors
|
|
22
|
-
} from '@/shared/image-attachments.js';
|
|
23
|
-
import { notifyRunFailed, notifyRunStopped } from '@/modules/notifications/index.js';
|
|
24
|
-
import { createCompleteMessage, createNormalizedMessage } from '@/shared/utils.js';
|
|
25
|
-
|
|
26
|
-
const activeCodexSessions = new Map();
|
|
27
|
-
|
|
28
|
-
function readUsageNumber(value) {
|
|
29
|
-
const parsed = Number(value);
|
|
30
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function extractCodexTokenBudget(event) {
|
|
34
|
-
const info = event?.info || event?.payload?.info || event?.usage?.info;
|
|
35
|
-
const usage = info?.total_token_usage || event?.usage?.total_token_usage || event?.usage;
|
|
36
|
-
if (!usage || typeof usage !== 'object') {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const inputTokens = readUsageNumber(usage.input_tokens);
|
|
41
|
-
const outputTokens = readUsageNumber(usage.output_tokens);
|
|
42
|
-
const used = readUsageNumber(usage.total_tokens) || inputTokens + outputTokens;
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
used,
|
|
46
|
-
total: readUsageNumber(info?.model_context_window || event?.usage?.model_context_window) || 200000,
|
|
47
|
-
inputTokens,
|
|
48
|
-
outputTokens,
|
|
49
|
-
breakdown: {
|
|
50
|
-
input: inputTokens,
|
|
51
|
-
output: outputTokens,
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Transform Codex SDK event to WebSocket message format
|
|
58
|
-
* @param {object} event - SDK event
|
|
59
|
-
* @returns {object} - Transformed event for WebSocket
|
|
60
|
-
*/
|
|
61
|
-
function transformCodexEvent(event) {
|
|
62
|
-
// Map SDK event types to a consistent format
|
|
63
|
-
switch (event.type) {
|
|
64
|
-
case 'item.started':
|
|
65
|
-
case 'item.updated':
|
|
66
|
-
case 'item.completed':
|
|
67
|
-
const item = event.item;
|
|
68
|
-
if (!item) {
|
|
69
|
-
return { type: event.type, item: null };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Transform based on item type
|
|
73
|
-
switch (item.type) {
|
|
74
|
-
case 'agent_message':
|
|
75
|
-
return {
|
|
76
|
-
type: 'item',
|
|
77
|
-
itemType: 'agent_message',
|
|
78
|
-
message: {
|
|
79
|
-
role: 'assistant',
|
|
80
|
-
content: item.text
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
case 'reasoning':
|
|
85
|
-
return {
|
|
86
|
-
type: 'item',
|
|
87
|
-
itemType: 'reasoning',
|
|
88
|
-
message: {
|
|
89
|
-
role: 'assistant',
|
|
90
|
-
content: item.text,
|
|
91
|
-
isReasoning: true
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
case 'command_execution':
|
|
96
|
-
return {
|
|
97
|
-
type: 'item',
|
|
98
|
-
itemType: 'command_execution',
|
|
99
|
-
command: item.command,
|
|
100
|
-
output: item.aggregated_output,
|
|
101
|
-
exitCode: item.exit_code,
|
|
102
|
-
status: item.status
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
case 'file_change':
|
|
106
|
-
return {
|
|
107
|
-
type: 'item',
|
|
108
|
-
itemType: 'file_change',
|
|
109
|
-
changes: item.changes,
|
|
110
|
-
status: item.status
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
case 'mcp_tool_call':
|
|
114
|
-
return {
|
|
115
|
-
type: 'item',
|
|
116
|
-
itemType: 'mcp_tool_call',
|
|
117
|
-
server: item.server,
|
|
118
|
-
tool: item.tool,
|
|
119
|
-
arguments: item.arguments,
|
|
120
|
-
result: item.result,
|
|
121
|
-
error: item.error,
|
|
122
|
-
status: item.status
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
case 'web_search':
|
|
126
|
-
return {
|
|
127
|
-
type: 'item',
|
|
128
|
-
itemType: 'web_search',
|
|
129
|
-
query: item.query
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
case 'todo_list':
|
|
133
|
-
return {
|
|
134
|
-
type: 'item',
|
|
135
|
-
itemType: 'todo_list',
|
|
136
|
-
items: item.items
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
case 'error':
|
|
140
|
-
return {
|
|
141
|
-
type: 'item',
|
|
142
|
-
itemType: 'error',
|
|
143
|
-
message: {
|
|
144
|
-
role: 'error',
|
|
145
|
-
content: item.message
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
default:
|
|
150
|
-
return {
|
|
151
|
-
type: 'item',
|
|
152
|
-
itemType: item.type,
|
|
153
|
-
item: item
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
case 'turn.started':
|
|
158
|
-
return {
|
|
159
|
-
type: 'turn_started'
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
case 'turn.completed':
|
|
163
|
-
return {
|
|
164
|
-
type: 'turn_complete',
|
|
165
|
-
usage: event.usage
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
case 'turn.failed':
|
|
169
|
-
return {
|
|
170
|
-
type: 'turn_failed',
|
|
171
|
-
error: event.error
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
case 'thread.started':
|
|
175
|
-
return {
|
|
176
|
-
type: 'thread_started',
|
|
177
|
-
threadId: event.thread_id || event.id
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
case 'error':
|
|
181
|
-
return {
|
|
182
|
-
type: 'error',
|
|
183
|
-
message: event.message
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
default:
|
|
187
|
-
return {
|
|
188
|
-
type: event.type,
|
|
189
|
-
data: event
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Map permission mode to Codex SDK options
|
|
196
|
-
* @param {string} permissionMode - 'default', 'acceptEdits', or 'bypassPermissions'
|
|
197
|
-
* @returns {object} - { sandboxMode, approvalPolicy }
|
|
198
|
-
*/
|
|
199
|
-
function mapPermissionModeToCodexOptions(permissionMode) {
|
|
200
|
-
switch (permissionMode) {
|
|
201
|
-
case 'acceptEdits':
|
|
202
|
-
return {
|
|
203
|
-
sandboxMode: 'workspace-write',
|
|
204
|
-
approvalPolicy: 'never'
|
|
205
|
-
};
|
|
206
|
-
case 'bypassPermissions':
|
|
207
|
-
return {
|
|
208
|
-
sandboxMode: 'danger-full-access',
|
|
209
|
-
approvalPolicy: 'never'
|
|
210
|
-
};
|
|
211
|
-
case 'default':
|
|
212
|
-
default:
|
|
213
|
-
return {
|
|
214
|
-
sandboxMode: 'workspace-write',
|
|
215
|
-
approvalPolicy: 'untrusted'
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Execute a Codex query with streaming
|
|
222
|
-
* @param {string} command - The prompt to send
|
|
223
|
-
* @param {object} options - Options including cwd, sessionId, model, permissionMode
|
|
224
|
-
* @param {WebSocket|object} ws - WebSocket connection or response writer
|
|
225
|
-
*/
|
|
226
|
-
export async function queryCodex(command, options = {}, ws, context) {
|
|
227
|
-
const {
|
|
228
|
-
sessionId,
|
|
229
|
-
sessionSummary,
|
|
230
|
-
cwd,
|
|
231
|
-
projectPath,
|
|
232
|
-
model,
|
|
233
|
-
effort,
|
|
234
|
-
images,
|
|
235
|
-
files,
|
|
236
|
-
permissionMode = 'default'
|
|
237
|
-
} = options;
|
|
238
|
-
|
|
239
|
-
// Callers pass the stable app session id; the SDK resumes threads with the
|
|
240
|
-
// provider-native id recorded on the session row.
|
|
241
|
-
const providerSessionId = context.resolveProviderSessionId(sessionId);
|
|
242
|
-
|
|
243
|
-
const resolvedModel = await context.resolveResumeModel(sessionId, model);
|
|
244
|
-
|
|
245
|
-
const workingDirectory = cwd || projectPath || process.cwd();
|
|
246
|
-
const { sandboxMode, approvalPolicy } = mapPermissionModeToCodexOptions(permissionMode);
|
|
247
|
-
const catalog = await context.getProviderModels();
|
|
248
|
-
const selectedModel = catalog.OPTIONS.find((option) => option.value === resolvedModel) || null;
|
|
249
|
-
const allowedEfforts = selectedModel?.effort?.values?.map((value) => value.value) || [];
|
|
250
|
-
const resolvedEffort = typeof effort === 'string' && effort !== 'default' && allowedEfforts.includes(effort)
|
|
251
|
-
? effort
|
|
252
|
-
: undefined;
|
|
253
|
-
|
|
254
|
-
let codex;
|
|
255
|
-
let thread;
|
|
256
|
-
// Provider-native thread id (starts as the resume id, or is captured from
|
|
257
|
-
// the stream for brand-new sessions).
|
|
258
|
-
let capturedSessionId = providerSessionId;
|
|
259
|
-
let sessionCreatedSent = false;
|
|
260
|
-
let terminalFailure = null;
|
|
261
|
-
const abortController = new AbortController();
|
|
262
|
-
// Session-map key: the app session id when the caller supplied one, else
|
|
263
|
-
// the provider-native thread id once captured (legacy/direct API callers).
|
|
264
|
-
const sessionKey = () => sessionId || capturedSessionId || null;
|
|
265
|
-
|
|
266
|
-
try {
|
|
267
|
-
codex = new Codex();
|
|
268
|
-
|
|
269
|
-
const threadOptions = {
|
|
270
|
-
workingDirectory,
|
|
271
|
-
skipGitRepoCheck: true,
|
|
272
|
-
sandboxMode,
|
|
273
|
-
approvalPolicy,
|
|
274
|
-
model: resolvedModel,
|
|
275
|
-
modelReasoningEffort: resolvedEffort,
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
if (providerSessionId) {
|
|
279
|
-
thread = codex.resumeThread(providerSessionId, threadOptions);
|
|
280
|
-
} else {
|
|
281
|
-
thread = codex.startThread(threadOptions);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const registerSession = (id) => {
|
|
285
|
-
if (!id) {
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
activeCodexSessions.set(id, {
|
|
289
|
-
thread,
|
|
290
|
-
codex,
|
|
291
|
-
status: 'running',
|
|
292
|
-
abortController,
|
|
293
|
-
startedAt: new Date().toISOString()
|
|
294
|
-
});
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
if (sessionKey()) {
|
|
298
|
-
registerSession(sessionKey());
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Execute with streaming. Turns with image attachments send structured
|
|
302
|
-
// input items so Codex reads the images from their local asset paths.
|
|
303
|
-
const promptWithFiles = appendFilesInputTag(command, files);
|
|
304
|
-
const turnInput = normalizeImageDescriptors(images).length > 0
|
|
305
|
-
? buildCodexInputItems(promptWithFiles, images, workingDirectory)
|
|
306
|
-
: promptWithFiles;
|
|
307
|
-
const streamedTurn = await thread.runStreamed(turnInput, {
|
|
308
|
-
signal: abortController.signal
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
for await (const event of streamedTurn.events) {
|
|
312
|
-
// Capture thread/session id lazily from the stream (Codex emits this asynchronously).
|
|
313
|
-
if (event.type === 'thread.started') {
|
|
314
|
-
const discoveredSessionId = event.thread_id || event.id || null;
|
|
315
|
-
if (discoveredSessionId && !capturedSessionId) {
|
|
316
|
-
capturedSessionId = discoveredSessionId;
|
|
317
|
-
registerSession(sessionKey());
|
|
318
|
-
|
|
319
|
-
if (ws.setSessionId && typeof ws.setSessionId === 'function') {
|
|
320
|
-
ws.setSessionId(capturedSessionId);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (!providerSessionId && !sessionCreatedSent) {
|
|
324
|
-
sessionCreatedSent = true;
|
|
325
|
-
sendMessage(ws, createNormalizedMessage({ kind: 'session_created', newSessionId: capturedSessionId, sessionId: capturedSessionId, provider: 'codex' }));
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Check if session was aborted
|
|
331
|
-
if (abortController.signal.aborted) {
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
if (sessionKey()) {
|
|
335
|
-
const session = activeCodexSessions.get(sessionKey());
|
|
336
|
-
if (session?.status === 'aborted') {
|
|
337
|
-
break;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (event.type === 'item.started' || event.type === 'item.updated') {
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const transformed = transformCodexEvent(event);
|
|
346
|
-
|
|
347
|
-
// Normalize the transformed event into NormalizedMessage(s) via adapter
|
|
348
|
-
const normalizedMsgs = context.normalizeMessage(transformed, capturedSessionId || sessionId || null);
|
|
349
|
-
for (const msg of normalizedMsgs) {
|
|
350
|
-
sendMessage(ws, msg);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (event.type === 'turn.failed' && !terminalFailure) {
|
|
354
|
-
terminalFailure = event.error || new Error('Turn failed');
|
|
355
|
-
// Notifications are app-facing, so they carry the app session id.
|
|
356
|
-
notifyRunFailed({
|
|
357
|
-
userId: ws?.userId || null,
|
|
358
|
-
provider: 'codex',
|
|
359
|
-
sessionId: sessionId || capturedSessionId || null,
|
|
360
|
-
sessionName: sessionSummary,
|
|
361
|
-
error: terminalFailure
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Extract and send token usage if available (normalized to match Claude format)
|
|
366
|
-
if (event.type === 'turn.completed') {
|
|
367
|
-
const tokenBudget = extractCodexTokenBudget(event);
|
|
368
|
-
if (tokenBudget) {
|
|
369
|
-
sendMessage(ws, createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget, sessionId: capturedSessionId || sessionId || null, provider: 'codex' }));
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Send the terminal completion event — skipped for aborted runs, whose
|
|
375
|
-
// terminal `complete` (aborted: true) was already sent by abort-session.
|
|
376
|
-
const runSession = sessionKey() ? activeCodexSessions.get(sessionKey()) : null;
|
|
377
|
-
const runAborted = runSession?.status === 'aborted' || abortController.signal.aborted;
|
|
378
|
-
if (!runAborted) {
|
|
379
|
-
sendMessage(ws, createCompleteMessage({
|
|
380
|
-
provider: 'codex',
|
|
381
|
-
sessionId: capturedSessionId || sessionId || null,
|
|
382
|
-
actualSessionId: capturedSessionId || thread.id || sessionId || null,
|
|
383
|
-
exitCode: terminalFailure ? 1 : 0,
|
|
384
|
-
}));
|
|
385
|
-
if (!terminalFailure) {
|
|
386
|
-
notifyRunStopped({
|
|
387
|
-
userId: ws?.userId || null,
|
|
388
|
-
provider: 'codex',
|
|
389
|
-
sessionId: sessionId || capturedSessionId || null,
|
|
390
|
-
sessionName: sessionSummary,
|
|
391
|
-
stopReason: 'completed'
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
} catch (error) {
|
|
397
|
-
const session = sessionKey() ? activeCodexSessions.get(sessionKey()) : null;
|
|
398
|
-
const wasAborted =
|
|
399
|
-
session?.status === 'aborted' ||
|
|
400
|
-
error?.name === 'AbortError' ||
|
|
401
|
-
String(error?.message || '').toLowerCase().includes('aborted');
|
|
402
|
-
|
|
403
|
-
if (!wasAborted) {
|
|
404
|
-
console.error('[Codex] Error:', error);
|
|
405
|
-
|
|
406
|
-
// Check if Codex SDK is available for a clearer error message
|
|
407
|
-
const installed = await context.isProviderInstalled();
|
|
408
|
-
const errorContent = !installed
|
|
409
|
-
? 'Codex CLI is not configured. Please set up authentication first.'
|
|
410
|
-
: error.message;
|
|
411
|
-
|
|
412
|
-
sendMessage(ws, createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: capturedSessionId || sessionId || null, provider: 'codex' }));
|
|
413
|
-
sendMessage(ws, createCompleteMessage({
|
|
414
|
-
provider: 'codex',
|
|
415
|
-
sessionId: capturedSessionId || sessionId || null,
|
|
416
|
-
exitCode: 1,
|
|
417
|
-
}));
|
|
418
|
-
if (!terminalFailure) {
|
|
419
|
-
notifyRunFailed({
|
|
420
|
-
userId: ws?.userId || null,
|
|
421
|
-
provider: 'codex',
|
|
422
|
-
sessionId: sessionId || capturedSessionId || null,
|
|
423
|
-
sessionName: sessionSummary,
|
|
424
|
-
error
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
} finally {
|
|
430
|
-
// Update session status
|
|
431
|
-
if (sessionKey()) {
|
|
432
|
-
const session = activeCodexSessions.get(sessionKey());
|
|
433
|
-
if (session) {
|
|
434
|
-
session.status = session.status === 'aborted' ? 'aborted' : 'completed';
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Abort an active Codex session
|
|
442
|
-
* @param {string} sessionId - Session ID to abort
|
|
443
|
-
* @returns {boolean} - Whether abort was successful
|
|
444
|
-
*/
|
|
445
|
-
export function abortCodexSession(sessionId) {
|
|
446
|
-
const session = activeCodexSessions.get(sessionId);
|
|
447
|
-
|
|
448
|
-
if (!session) {
|
|
449
|
-
return false;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
session.status = 'aborted';
|
|
453
|
-
try {
|
|
454
|
-
session.abortController?.abort();
|
|
455
|
-
} catch (error) {
|
|
456
|
-
console.warn(`[Codex] Failed to abort session ${sessionId}:`, error);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
return true;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Check if a session is active
|
|
464
|
-
* @param {string} sessionId - Session ID to check
|
|
465
|
-
* @returns {boolean} - Whether session is active
|
|
466
|
-
*/
|
|
467
|
-
export function isCodexSessionActive(sessionId) {
|
|
468
|
-
const session = activeCodexSessions.get(sessionId);
|
|
469
|
-
return session?.status === 'running';
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Get all active sessions
|
|
474
|
-
* @returns {Array} - Array of active session info
|
|
475
|
-
*/
|
|
476
|
-
export function getActiveCodexSessions() {
|
|
477
|
-
const sessions = [];
|
|
478
|
-
|
|
479
|
-
for (const [id, session] of activeCodexSessions.entries()) {
|
|
480
|
-
if (session.status === 'running') {
|
|
481
|
-
sessions.push({
|
|
482
|
-
id,
|
|
483
|
-
status: session.status,
|
|
484
|
-
startedAt: session.startedAt
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
return sessions;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
export const codexRuntime = {
|
|
493
|
-
run: queryCodex,
|
|
494
|
-
abort: abortCodexSession,
|
|
495
|
-
};
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Helper to send message via WebSocket or writer
|
|
499
|
-
* @param {WebSocket|object} ws - WebSocket or response writer
|
|
500
|
-
* @param {object} data - Data to send
|
|
501
|
-
*/
|
|
502
|
-
function sendMessage(ws, data) {
|
|
503
|
-
try {
|
|
504
|
-
if (ws.isSSEStreamWriter || ws.isWebSocketWriter) {
|
|
505
|
-
// Writer handles stringification (SSEStreamWriter or WebSocketWriter)
|
|
506
|
-
ws.send(data);
|
|
507
|
-
} else if (typeof ws.send === 'function') {
|
|
508
|
-
// Raw WebSocket - stringify here
|
|
509
|
-
ws.send(JSON.stringify(data));
|
|
510
|
-
}
|
|
511
|
-
} catch (error) {
|
|
512
|
-
console.error('[Codex] Error sending message:', error);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Clean up old completed sessions periodically
|
|
517
|
-
const completedSessionCleanupTimer = setInterval(() => {
|
|
518
|
-
const now = Date.now();
|
|
519
|
-
const maxAge = 30 * 60 * 1000; // 30 minutes
|
|
520
|
-
|
|
521
|
-
for (const [id, session] of activeCodexSessions.entries()) {
|
|
522
|
-
if (session.status !== 'running') {
|
|
523
|
-
const startedAt = new Date(session.startedAt).getTime();
|
|
524
|
-
if (now - startedAt > maxAge) {
|
|
525
|
-
activeCodexSessions.delete(id);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}, 5 * 60 * 1000); // Every 5 minutes
|
|
530
|
-
|
|
531
|
-
// Runtime cleanup should not keep focused tests or one-off scripts alive after
|
|
532
|
-
// their provider work has completed.
|
|
533
|
-
completedSessionCleanupTimer.unref?.();
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Codex SDK Integration
|
|
3
|
+
* =============================
|
|
4
|
+
*
|
|
5
|
+
* This module provides integration with the OpenAI Codex SDK for non-interactive
|
|
6
|
+
* chat sessions. It mirrors the Claude runtime adapter for consistency.
|
|
7
|
+
*
|
|
8
|
+
* ## Usage
|
|
9
|
+
*
|
|
10
|
+
* - codexRuntime.run(command, options, writer, context) - Execute a streamed prompt
|
|
11
|
+
* - abortCodexSession(sessionId) - Cancel an active session
|
|
12
|
+
* - isCodexSessionActive(sessionId) - Check if a session is running
|
|
13
|
+
* - getActiveCodexSessions() - List all active sessions
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Codex } from '@openai/codex-sdk';
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
appendFilesInputTag,
|
|
20
|
+
buildCodexInputItems,
|
|
21
|
+
normalizeImageDescriptors
|
|
22
|
+
} from '@/shared/image-attachments.js';
|
|
23
|
+
import { notifyRunFailed, notifyRunStopped } from '@/modules/notifications/index.js';
|
|
24
|
+
import { createCompleteMessage, createNormalizedMessage } from '@/shared/utils.js';
|
|
25
|
+
|
|
26
|
+
const activeCodexSessions = new Map();
|
|
27
|
+
|
|
28
|
+
function readUsageNumber(value) {
|
|
29
|
+
const parsed = Number(value);
|
|
30
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extractCodexTokenBudget(event) {
|
|
34
|
+
const info = event?.info || event?.payload?.info || event?.usage?.info;
|
|
35
|
+
const usage = info?.total_token_usage || event?.usage?.total_token_usage || event?.usage;
|
|
36
|
+
if (!usage || typeof usage !== 'object') {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const inputTokens = readUsageNumber(usage.input_tokens);
|
|
41
|
+
const outputTokens = readUsageNumber(usage.output_tokens);
|
|
42
|
+
const used = readUsageNumber(usage.total_tokens) || inputTokens + outputTokens;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
used,
|
|
46
|
+
total: readUsageNumber(info?.model_context_window || event?.usage?.model_context_window) || 200000,
|
|
47
|
+
inputTokens,
|
|
48
|
+
outputTokens,
|
|
49
|
+
breakdown: {
|
|
50
|
+
input: inputTokens,
|
|
51
|
+
output: outputTokens,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Transform Codex SDK event to WebSocket message format
|
|
58
|
+
* @param {object} event - SDK event
|
|
59
|
+
* @returns {object} - Transformed event for WebSocket
|
|
60
|
+
*/
|
|
61
|
+
function transformCodexEvent(event) {
|
|
62
|
+
// Map SDK event types to a consistent format
|
|
63
|
+
switch (event.type) {
|
|
64
|
+
case 'item.started':
|
|
65
|
+
case 'item.updated':
|
|
66
|
+
case 'item.completed':
|
|
67
|
+
const item = event.item;
|
|
68
|
+
if (!item) {
|
|
69
|
+
return { type: event.type, item: null };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Transform based on item type
|
|
73
|
+
switch (item.type) {
|
|
74
|
+
case 'agent_message':
|
|
75
|
+
return {
|
|
76
|
+
type: 'item',
|
|
77
|
+
itemType: 'agent_message',
|
|
78
|
+
message: {
|
|
79
|
+
role: 'assistant',
|
|
80
|
+
content: item.text
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
case 'reasoning':
|
|
85
|
+
return {
|
|
86
|
+
type: 'item',
|
|
87
|
+
itemType: 'reasoning',
|
|
88
|
+
message: {
|
|
89
|
+
role: 'assistant',
|
|
90
|
+
content: item.text,
|
|
91
|
+
isReasoning: true
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
case 'command_execution':
|
|
96
|
+
return {
|
|
97
|
+
type: 'item',
|
|
98
|
+
itemType: 'command_execution',
|
|
99
|
+
command: item.command,
|
|
100
|
+
output: item.aggregated_output,
|
|
101
|
+
exitCode: item.exit_code,
|
|
102
|
+
status: item.status
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
case 'file_change':
|
|
106
|
+
return {
|
|
107
|
+
type: 'item',
|
|
108
|
+
itemType: 'file_change',
|
|
109
|
+
changes: item.changes,
|
|
110
|
+
status: item.status
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
case 'mcp_tool_call':
|
|
114
|
+
return {
|
|
115
|
+
type: 'item',
|
|
116
|
+
itemType: 'mcp_tool_call',
|
|
117
|
+
server: item.server,
|
|
118
|
+
tool: item.tool,
|
|
119
|
+
arguments: item.arguments,
|
|
120
|
+
result: item.result,
|
|
121
|
+
error: item.error,
|
|
122
|
+
status: item.status
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
case 'web_search':
|
|
126
|
+
return {
|
|
127
|
+
type: 'item',
|
|
128
|
+
itemType: 'web_search',
|
|
129
|
+
query: item.query
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
case 'todo_list':
|
|
133
|
+
return {
|
|
134
|
+
type: 'item',
|
|
135
|
+
itemType: 'todo_list',
|
|
136
|
+
items: item.items
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
case 'error':
|
|
140
|
+
return {
|
|
141
|
+
type: 'item',
|
|
142
|
+
itemType: 'error',
|
|
143
|
+
message: {
|
|
144
|
+
role: 'error',
|
|
145
|
+
content: item.message
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
default:
|
|
150
|
+
return {
|
|
151
|
+
type: 'item',
|
|
152
|
+
itemType: item.type,
|
|
153
|
+
item: item
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
case 'turn.started':
|
|
158
|
+
return {
|
|
159
|
+
type: 'turn_started'
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
case 'turn.completed':
|
|
163
|
+
return {
|
|
164
|
+
type: 'turn_complete',
|
|
165
|
+
usage: event.usage
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
case 'turn.failed':
|
|
169
|
+
return {
|
|
170
|
+
type: 'turn_failed',
|
|
171
|
+
error: event.error
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
case 'thread.started':
|
|
175
|
+
return {
|
|
176
|
+
type: 'thread_started',
|
|
177
|
+
threadId: event.thread_id || event.id
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
case 'error':
|
|
181
|
+
return {
|
|
182
|
+
type: 'error',
|
|
183
|
+
message: event.message
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
default:
|
|
187
|
+
return {
|
|
188
|
+
type: event.type,
|
|
189
|
+
data: event
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Map permission mode to Codex SDK options
|
|
196
|
+
* @param {string} permissionMode - 'default', 'acceptEdits', or 'bypassPermissions'
|
|
197
|
+
* @returns {object} - { sandboxMode, approvalPolicy }
|
|
198
|
+
*/
|
|
199
|
+
function mapPermissionModeToCodexOptions(permissionMode) {
|
|
200
|
+
switch (permissionMode) {
|
|
201
|
+
case 'acceptEdits':
|
|
202
|
+
return {
|
|
203
|
+
sandboxMode: 'workspace-write',
|
|
204
|
+
approvalPolicy: 'never'
|
|
205
|
+
};
|
|
206
|
+
case 'bypassPermissions':
|
|
207
|
+
return {
|
|
208
|
+
sandboxMode: 'danger-full-access',
|
|
209
|
+
approvalPolicy: 'never'
|
|
210
|
+
};
|
|
211
|
+
case 'default':
|
|
212
|
+
default:
|
|
213
|
+
return {
|
|
214
|
+
sandboxMode: 'workspace-write',
|
|
215
|
+
approvalPolicy: 'untrusted'
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Execute a Codex query with streaming
|
|
222
|
+
* @param {string} command - The prompt to send
|
|
223
|
+
* @param {object} options - Options including cwd, sessionId, model, permissionMode
|
|
224
|
+
* @param {WebSocket|object} ws - WebSocket connection or response writer
|
|
225
|
+
*/
|
|
226
|
+
export async function queryCodex(command, options = {}, ws, context) {
|
|
227
|
+
const {
|
|
228
|
+
sessionId,
|
|
229
|
+
sessionSummary,
|
|
230
|
+
cwd,
|
|
231
|
+
projectPath,
|
|
232
|
+
model,
|
|
233
|
+
effort,
|
|
234
|
+
images,
|
|
235
|
+
files,
|
|
236
|
+
permissionMode = 'default'
|
|
237
|
+
} = options;
|
|
238
|
+
|
|
239
|
+
// Callers pass the stable app session id; the SDK resumes threads with the
|
|
240
|
+
// provider-native id recorded on the session row.
|
|
241
|
+
const providerSessionId = context.resolveProviderSessionId(sessionId);
|
|
242
|
+
|
|
243
|
+
const resolvedModel = await context.resolveResumeModel(sessionId, model);
|
|
244
|
+
|
|
245
|
+
const workingDirectory = cwd || projectPath || process.cwd();
|
|
246
|
+
const { sandboxMode, approvalPolicy } = mapPermissionModeToCodexOptions(permissionMode);
|
|
247
|
+
const catalog = await context.getProviderModels();
|
|
248
|
+
const selectedModel = catalog.OPTIONS.find((option) => option.value === resolvedModel) || null;
|
|
249
|
+
const allowedEfforts = selectedModel?.effort?.values?.map((value) => value.value) || [];
|
|
250
|
+
const resolvedEffort = typeof effort === 'string' && effort !== 'default' && allowedEfforts.includes(effort)
|
|
251
|
+
? effort
|
|
252
|
+
: undefined;
|
|
253
|
+
|
|
254
|
+
let codex;
|
|
255
|
+
let thread;
|
|
256
|
+
// Provider-native thread id (starts as the resume id, or is captured from
|
|
257
|
+
// the stream for brand-new sessions).
|
|
258
|
+
let capturedSessionId = providerSessionId;
|
|
259
|
+
let sessionCreatedSent = false;
|
|
260
|
+
let terminalFailure = null;
|
|
261
|
+
const abortController = new AbortController();
|
|
262
|
+
// Session-map key: the app session id when the caller supplied one, else
|
|
263
|
+
// the provider-native thread id once captured (legacy/direct API callers).
|
|
264
|
+
const sessionKey = () => sessionId || capturedSessionId || null;
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
codex = new Codex();
|
|
268
|
+
|
|
269
|
+
const threadOptions = {
|
|
270
|
+
workingDirectory,
|
|
271
|
+
skipGitRepoCheck: true,
|
|
272
|
+
sandboxMode,
|
|
273
|
+
approvalPolicy,
|
|
274
|
+
model: resolvedModel,
|
|
275
|
+
modelReasoningEffort: resolvedEffort,
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
if (providerSessionId) {
|
|
279
|
+
thread = codex.resumeThread(providerSessionId, threadOptions);
|
|
280
|
+
} else {
|
|
281
|
+
thread = codex.startThread(threadOptions);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const registerSession = (id) => {
|
|
285
|
+
if (!id) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
activeCodexSessions.set(id, {
|
|
289
|
+
thread,
|
|
290
|
+
codex,
|
|
291
|
+
status: 'running',
|
|
292
|
+
abortController,
|
|
293
|
+
startedAt: new Date().toISOString()
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (sessionKey()) {
|
|
298
|
+
registerSession(sessionKey());
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Execute with streaming. Turns with image attachments send structured
|
|
302
|
+
// input items so Codex reads the images from their local asset paths.
|
|
303
|
+
const promptWithFiles = appendFilesInputTag(command, files);
|
|
304
|
+
const turnInput = normalizeImageDescriptors(images).length > 0
|
|
305
|
+
? buildCodexInputItems(promptWithFiles, images, workingDirectory)
|
|
306
|
+
: promptWithFiles;
|
|
307
|
+
const streamedTurn = await thread.runStreamed(turnInput, {
|
|
308
|
+
signal: abortController.signal
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
for await (const event of streamedTurn.events) {
|
|
312
|
+
// Capture thread/session id lazily from the stream (Codex emits this asynchronously).
|
|
313
|
+
if (event.type === 'thread.started') {
|
|
314
|
+
const discoveredSessionId = event.thread_id || event.id || null;
|
|
315
|
+
if (discoveredSessionId && !capturedSessionId) {
|
|
316
|
+
capturedSessionId = discoveredSessionId;
|
|
317
|
+
registerSession(sessionKey());
|
|
318
|
+
|
|
319
|
+
if (ws.setSessionId && typeof ws.setSessionId === 'function') {
|
|
320
|
+
ws.setSessionId(capturedSessionId);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (!providerSessionId && !sessionCreatedSent) {
|
|
324
|
+
sessionCreatedSent = true;
|
|
325
|
+
sendMessage(ws, createNormalizedMessage({ kind: 'session_created', newSessionId: capturedSessionId, sessionId: capturedSessionId, provider: 'codex' }));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Check if session was aborted
|
|
331
|
+
if (abortController.signal.aborted) {
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
if (sessionKey()) {
|
|
335
|
+
const session = activeCodexSessions.get(sessionKey());
|
|
336
|
+
if (session?.status === 'aborted') {
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (event.type === 'item.started' || event.type === 'item.updated') {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const transformed = transformCodexEvent(event);
|
|
346
|
+
|
|
347
|
+
// Normalize the transformed event into NormalizedMessage(s) via adapter
|
|
348
|
+
const normalizedMsgs = context.normalizeMessage(transformed, capturedSessionId || sessionId || null);
|
|
349
|
+
for (const msg of normalizedMsgs) {
|
|
350
|
+
sendMessage(ws, msg);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (event.type === 'turn.failed' && !terminalFailure) {
|
|
354
|
+
terminalFailure = event.error || new Error('Turn failed');
|
|
355
|
+
// Notifications are app-facing, so they carry the app session id.
|
|
356
|
+
notifyRunFailed({
|
|
357
|
+
userId: ws?.userId || null,
|
|
358
|
+
provider: 'codex',
|
|
359
|
+
sessionId: sessionId || capturedSessionId || null,
|
|
360
|
+
sessionName: sessionSummary,
|
|
361
|
+
error: terminalFailure
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Extract and send token usage if available (normalized to match Claude format)
|
|
366
|
+
if (event.type === 'turn.completed') {
|
|
367
|
+
const tokenBudget = extractCodexTokenBudget(event);
|
|
368
|
+
if (tokenBudget) {
|
|
369
|
+
sendMessage(ws, createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget, sessionId: capturedSessionId || sessionId || null, provider: 'codex' }));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Send the terminal completion event — skipped for aborted runs, whose
|
|
375
|
+
// terminal `complete` (aborted: true) was already sent by abort-session.
|
|
376
|
+
const runSession = sessionKey() ? activeCodexSessions.get(sessionKey()) : null;
|
|
377
|
+
const runAborted = runSession?.status === 'aborted' || abortController.signal.aborted;
|
|
378
|
+
if (!runAborted) {
|
|
379
|
+
sendMessage(ws, createCompleteMessage({
|
|
380
|
+
provider: 'codex',
|
|
381
|
+
sessionId: capturedSessionId || sessionId || null,
|
|
382
|
+
actualSessionId: capturedSessionId || thread.id || sessionId || null,
|
|
383
|
+
exitCode: terminalFailure ? 1 : 0,
|
|
384
|
+
}));
|
|
385
|
+
if (!terminalFailure) {
|
|
386
|
+
notifyRunStopped({
|
|
387
|
+
userId: ws?.userId || null,
|
|
388
|
+
provider: 'codex',
|
|
389
|
+
sessionId: sessionId || capturedSessionId || null,
|
|
390
|
+
sessionName: sessionSummary,
|
|
391
|
+
stopReason: 'completed'
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
} catch (error) {
|
|
397
|
+
const session = sessionKey() ? activeCodexSessions.get(sessionKey()) : null;
|
|
398
|
+
const wasAborted =
|
|
399
|
+
session?.status === 'aborted' ||
|
|
400
|
+
error?.name === 'AbortError' ||
|
|
401
|
+
String(error?.message || '').toLowerCase().includes('aborted');
|
|
402
|
+
|
|
403
|
+
if (!wasAborted) {
|
|
404
|
+
console.error('[Codex] Error:', error);
|
|
405
|
+
|
|
406
|
+
// Check if Codex SDK is available for a clearer error message
|
|
407
|
+
const installed = await context.isProviderInstalled();
|
|
408
|
+
const errorContent = !installed
|
|
409
|
+
? 'Codex CLI is not configured. Please set up authentication first.'
|
|
410
|
+
: error.message;
|
|
411
|
+
|
|
412
|
+
sendMessage(ws, createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: capturedSessionId || sessionId || null, provider: 'codex' }));
|
|
413
|
+
sendMessage(ws, createCompleteMessage({
|
|
414
|
+
provider: 'codex',
|
|
415
|
+
sessionId: capturedSessionId || sessionId || null,
|
|
416
|
+
exitCode: 1,
|
|
417
|
+
}));
|
|
418
|
+
if (!terminalFailure) {
|
|
419
|
+
notifyRunFailed({
|
|
420
|
+
userId: ws?.userId || null,
|
|
421
|
+
provider: 'codex',
|
|
422
|
+
sessionId: sessionId || capturedSessionId || null,
|
|
423
|
+
sessionName: sessionSummary,
|
|
424
|
+
error
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
} finally {
|
|
430
|
+
// Update session status
|
|
431
|
+
if (sessionKey()) {
|
|
432
|
+
const session = activeCodexSessions.get(sessionKey());
|
|
433
|
+
if (session) {
|
|
434
|
+
session.status = session.status === 'aborted' ? 'aborted' : 'completed';
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Abort an active Codex session
|
|
442
|
+
* @param {string} sessionId - Session ID to abort
|
|
443
|
+
* @returns {boolean} - Whether abort was successful
|
|
444
|
+
*/
|
|
445
|
+
export function abortCodexSession(sessionId) {
|
|
446
|
+
const session = activeCodexSessions.get(sessionId);
|
|
447
|
+
|
|
448
|
+
if (!session) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
session.status = 'aborted';
|
|
453
|
+
try {
|
|
454
|
+
session.abortController?.abort();
|
|
455
|
+
} catch (error) {
|
|
456
|
+
console.warn(`[Codex] Failed to abort session ${sessionId}:`, error);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Check if a session is active
|
|
464
|
+
* @param {string} sessionId - Session ID to check
|
|
465
|
+
* @returns {boolean} - Whether session is active
|
|
466
|
+
*/
|
|
467
|
+
export function isCodexSessionActive(sessionId) {
|
|
468
|
+
const session = activeCodexSessions.get(sessionId);
|
|
469
|
+
return session?.status === 'running';
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Get all active sessions
|
|
474
|
+
* @returns {Array} - Array of active session info
|
|
475
|
+
*/
|
|
476
|
+
export function getActiveCodexSessions() {
|
|
477
|
+
const sessions = [];
|
|
478
|
+
|
|
479
|
+
for (const [id, session] of activeCodexSessions.entries()) {
|
|
480
|
+
if (session.status === 'running') {
|
|
481
|
+
sessions.push({
|
|
482
|
+
id,
|
|
483
|
+
status: session.status,
|
|
484
|
+
startedAt: session.startedAt
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return sessions;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export const codexRuntime = {
|
|
493
|
+
run: queryCodex,
|
|
494
|
+
abort: abortCodexSession,
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Helper to send message via WebSocket or writer
|
|
499
|
+
* @param {WebSocket|object} ws - WebSocket or response writer
|
|
500
|
+
* @param {object} data - Data to send
|
|
501
|
+
*/
|
|
502
|
+
function sendMessage(ws, data) {
|
|
503
|
+
try {
|
|
504
|
+
if (ws.isSSEStreamWriter || ws.isWebSocketWriter) {
|
|
505
|
+
// Writer handles stringification (SSEStreamWriter or WebSocketWriter)
|
|
506
|
+
ws.send(data);
|
|
507
|
+
} else if (typeof ws.send === 'function') {
|
|
508
|
+
// Raw WebSocket - stringify here
|
|
509
|
+
ws.send(JSON.stringify(data));
|
|
510
|
+
}
|
|
511
|
+
} catch (error) {
|
|
512
|
+
console.error('[Codex] Error sending message:', error);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Clean up old completed sessions periodically
|
|
517
|
+
const completedSessionCleanupTimer = setInterval(() => {
|
|
518
|
+
const now = Date.now();
|
|
519
|
+
const maxAge = 30 * 60 * 1000; // 30 minutes
|
|
520
|
+
|
|
521
|
+
for (const [id, session] of activeCodexSessions.entries()) {
|
|
522
|
+
if (session.status !== 'running') {
|
|
523
|
+
const startedAt = new Date(session.startedAt).getTime();
|
|
524
|
+
if (now - startedAt > maxAge) {
|
|
525
|
+
activeCodexSessions.delete(id);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}, 5 * 60 * 1000); // Every 5 minutes
|
|
530
|
+
|
|
531
|
+
// Runtime cleanup should not keep focused tests or one-off scripts alive after
|
|
532
|
+
// their provider work has completed.
|
|
533
|
+
completedSessionCleanupTimer.unref?.();
|