@skaile/workspaces 6.9.0 → 6.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/CHANGELOG.md +32 -0
- package/dist/ai-sdk.js +8 -8
- package/dist/{asset-feeds-JWRIWLVX.js → asset-feeds-UG3PG423.js} +22 -22
- package/dist/{asset-feeds-JWRIWLVX.js.map → asset-feeds-UG3PG423.js.map} +1 -1
- package/dist/asset-manager/catalog-deployer.js +8 -8
- package/dist/asset-manager/history.js +8 -8
- package/dist/asset-manager/index.js +14 -14
- package/dist/asset-manager/installer.js +9 -9
- package/dist/asset-manager/renderers.js +8 -8
- package/dist/asset-manager/src/harness-switch-state.d.ts +30 -0
- package/dist/asset-manager/src/harness-switch-state.d.ts.map +1 -0
- package/dist/asset-manager/src/index.d.ts +49 -12
- package/dist/asset-manager/src/index.d.ts.map +1 -1
- package/dist/asset-manager/src/mcp-config.d.ts +6 -0
- package/dist/asset-manager/src/mcp-config.d.ts.map +1 -1
- package/dist/bridge/codex-app-server.js +1 -2
- package/dist/bridge/harnesses/claude-sdk.js +11 -11
- package/dist/bridge/harnesses/codex.js +15 -1926
- package/dist/bridge/harnesses/codex.js.map +1 -1
- package/dist/bridge/harnesses/continue.js +11 -11
- package/dist/bridge/harnesses/echo.js +3 -3
- package/dist/bridge/harnesses/gemini.js +6 -6
- package/dist/bridge/harnesses/goose.js +11 -11
- package/dist/bridge/harnesses/omp.js +61 -9
- package/dist/bridge/harnesses/omp.js.map +1 -1
- package/dist/bridge/harnesses/opencode.js +11 -11
- package/dist/bridge/harnesses/qwen.js +6 -6
- package/dist/bridge/index.js +17 -16
- package/dist/bridge/src/harness-model-check.d.ts +5 -4
- package/dist/bridge/src/harness-model-check.d.ts.map +1 -1
- package/dist/bridge/src/harnesses/codex.d.ts +13 -0
- package/dist/bridge/src/harnesses/codex.d.ts.map +1 -1
- package/dist/bridge/src/harnesses/omp.d.ts +22 -0
- package/dist/bridge/src/harnesses/omp.d.ts.map +1 -1
- package/dist/bridge/src/index.d.ts +1 -0
- package/dist/bridge/src/index.d.ts.map +1 -1
- package/dist/{chunk-XPKUFOS3.js → chunk-2D6HZAO5.js} +8 -8
- package/dist/{chunk-XPKUFOS3.js.map → chunk-2D6HZAO5.js.map} +1 -1
- package/dist/chunk-2MUUPRIG.js +688 -0
- package/dist/chunk-2MUUPRIG.js.map +1 -0
- package/dist/{chunk-TMQFOJ6F.js → chunk-2PVYDAFN.js} +3 -3
- package/dist/{chunk-TMQFOJ6F.js.map → chunk-2PVYDAFN.js.map} +1 -1
- package/dist/{chunk-YGT3CGPD.js → chunk-2U67XYZM.js} +3 -3
- package/dist/{chunk-YGT3CGPD.js.map → chunk-2U67XYZM.js.map} +1 -1
- package/dist/{chunk-5NTB64PW.js → chunk-375HN3FD.js} +18 -14
- package/dist/chunk-375HN3FD.js.map +1 -0
- package/dist/{chunk-23Q5LT4U.js → chunk-4I4SBOCS.js} +345 -88
- package/dist/chunk-4I4SBOCS.js.map +1 -0
- package/dist/{chunk-WPLZGT5Z.js → chunk-4Z4RULWQ.js} +59 -31
- package/dist/chunk-4Z4RULWQ.js.map +1 -0
- package/dist/{chunk-72BDCGBB.js → chunk-5JBMYZNA.js} +3 -3
- package/dist/{chunk-72BDCGBB.js.map → chunk-5JBMYZNA.js.map} +1 -1
- package/dist/{chunk-3EPRTMXY.js → chunk-5KQFR4D7.js} +6 -6
- package/dist/{chunk-3EPRTMXY.js.map → chunk-5KQFR4D7.js.map} +1 -1
- package/dist/{chunk-KNY356I3.js → chunk-5KSGZP3E.js} +11 -4
- package/dist/chunk-5KSGZP3E.js.map +1 -0
- package/dist/{chunk-DZRN3SNG.js → chunk-5P7LTPBR.js} +5 -5
- package/dist/{chunk-DZRN3SNG.js.map → chunk-5P7LTPBR.js.map} +1 -1
- package/dist/{chunk-PTJZCOBH.js → chunk-6O7VDKX3.js} +2 -2
- package/dist/{chunk-PTJZCOBH.js.map → chunk-6O7VDKX3.js.map} +1 -1
- package/dist/{chunk-B2DGCYVC.js → chunk-7DUOKE73.js} +27 -33
- package/dist/chunk-7DUOKE73.js.map +1 -0
- package/dist/{chunk-V5KG6CCV.js → chunk-7HRZACI4.js} +7 -2
- package/dist/chunk-7HRZACI4.js.map +1 -0
- package/dist/{chunk-U3FI4DVT.js → chunk-7K73MCI6.js} +7 -5
- package/dist/chunk-7K73MCI6.js.map +1 -0
- package/dist/{chunk-SE6OL7Y5.js → chunk-BCID52M5.js} +6 -6
- package/dist/{chunk-SE6OL7Y5.js.map → chunk-BCID52M5.js.map} +1 -1
- package/dist/{chunk-FZO64PLQ.js → chunk-BNLXZXZN.js} +81 -8
- package/dist/chunk-BNLXZXZN.js.map +1 -0
- package/dist/{chunk-O3FYOS64.js → chunk-C5UKYTTD.js} +13 -23
- package/dist/chunk-C5UKYTTD.js.map +1 -0
- package/dist/{chunk-LYVLDGML.js → chunk-CG63XGVV.js} +3 -3
- package/dist/{chunk-LYVLDGML.js.map → chunk-CG63XGVV.js.map} +1 -1
- package/dist/{chunk-4ROXSQEG.js → chunk-CJO3XKO7.js} +4 -4
- package/dist/{chunk-4ROXSQEG.js.map → chunk-CJO3XKO7.js.map} +1 -1
- package/dist/{chunk-OSABUEWA.js → chunk-CVUMZTNO.js} +4 -4
- package/dist/{chunk-OSABUEWA.js.map → chunk-CVUMZTNO.js.map} +1 -1
- package/dist/{chunk-E675QKFG.js → chunk-DOMRBOJZ.js} +5 -5
- package/dist/{chunk-E675QKFG.js.map → chunk-DOMRBOJZ.js.map} +1 -1
- package/dist/{chunk-GTAHU4N4.js → chunk-EB2LKPI6.js} +4 -4
- package/dist/{chunk-GTAHU4N4.js.map → chunk-EB2LKPI6.js.map} +1 -1
- package/dist/{chunk-GZL6RN4Z.js → chunk-FXBKBOI5.js} +169 -49
- package/dist/chunk-FXBKBOI5.js.map +1 -0
- package/dist/{chunk-AERQFD6T.js → chunk-GA4GUDTL.js} +4 -4
- package/dist/{chunk-AERQFD6T.js.map → chunk-GA4GUDTL.js.map} +1 -1
- package/dist/{chunk-NJVHGS7X.js → chunk-HE3TZYNG.js} +4 -4
- package/dist/{chunk-NJVHGS7X.js.map → chunk-HE3TZYNG.js.map} +1 -1
- package/dist/chunk-HLFLET7S.js +2443 -0
- package/dist/chunk-HLFLET7S.js.map +1 -0
- package/dist/{chunk-Y2BQWZXT.js → chunk-HQUF6O4B.js} +27 -56
- package/dist/chunk-HQUF6O4B.js.map +1 -0
- package/dist/{chunk-T5EIWWEJ.js → chunk-IMO4EHND.js} +5 -5
- package/dist/{chunk-T5EIWWEJ.js.map → chunk-IMO4EHND.js.map} +1 -1
- package/dist/{chunk-TUOHA6BM.js → chunk-IOXFT5D7.js} +3 -3
- package/dist/{chunk-TUOHA6BM.js.map → chunk-IOXFT5D7.js.map} +1 -1
- package/dist/{chunk-5AQLCRR7.js → chunk-JDZUQFN7.js} +4 -4
- package/dist/{chunk-5AQLCRR7.js.map → chunk-JDZUQFN7.js.map} +1 -1
- package/dist/{chunk-SQMDFM4N.js → chunk-KE5HDORU.js} +2 -2
- package/dist/{chunk-SQMDFM4N.js.map → chunk-KE5HDORU.js.map} +1 -1
- package/dist/{chunk-PTJTOPE5.js → chunk-KOI7QFDH.js} +3 -3
- package/dist/{chunk-PTJTOPE5.js.map → chunk-KOI7QFDH.js.map} +1 -1
- package/dist/{chunk-NDBHEOIA.js → chunk-KRL53RZB.js} +8 -8
- package/dist/{chunk-NDBHEOIA.js.map → chunk-KRL53RZB.js.map} +1 -1
- package/dist/{chunk-7QYBKUAS.js → chunk-LKBRBVVS.js} +3 -3
- package/dist/{chunk-7QYBKUAS.js.map → chunk-LKBRBVVS.js.map} +1 -1
- package/dist/{chunk-USSP43U3.js → chunk-LQWOU7S2.js} +3 -3
- package/dist/{chunk-USSP43U3.js.map → chunk-LQWOU7S2.js.map} +1 -1
- package/dist/{chunk-4KPQ6ILH.js → chunk-MEU2HHGV.js} +18 -9
- package/dist/chunk-MEU2HHGV.js.map +1 -0
- package/dist/{chunk-QC5BYUED.js → chunk-MS2IXLTC.js} +4 -4
- package/dist/{chunk-QC5BYUED.js.map → chunk-MS2IXLTC.js.map} +1 -1
- package/dist/{chunk-X2B5GMJO.js → chunk-NCJSYU6A.js} +16 -14
- package/dist/chunk-NCJSYU6A.js.map +1 -0
- package/dist/{chunk-CFTTXUM2.js → chunk-NFFBYG2W.js} +3 -3
- package/dist/{chunk-CFTTXUM2.js.map → chunk-NFFBYG2W.js.map} +1 -1
- package/dist/{chunk-AVRTZAH2.js → chunk-NTFP2XF4.js} +10 -10
- package/dist/chunk-NTFP2XF4.js.map +1 -0
- package/dist/{chunk-V2CONXKD.js → chunk-PSPFWTJI.js} +4 -4
- package/dist/{chunk-V2CONXKD.js.map → chunk-PSPFWTJI.js.map} +1 -1
- package/dist/{chunk-C7L5MFUL.js → chunk-PTXQMTCG.js} +3 -3
- package/dist/{chunk-C7L5MFUL.js.map → chunk-PTXQMTCG.js.map} +1 -1
- package/dist/{chunk-Y242OE4B.js → chunk-QBJQ426X.js} +3 -3
- package/dist/{chunk-Y242OE4B.js.map → chunk-QBJQ426X.js.map} +1 -1
- package/dist/{chunk-QBUJTVMY.js → chunk-R3ZNAPBV.js} +3 -3
- package/dist/{chunk-QBUJTVMY.js.map → chunk-R3ZNAPBV.js.map} +1 -1
- package/dist/{chunk-4WB4KSSQ.js → chunk-RTFH2QR2.js} +3 -3
- package/dist/{chunk-4WB4KSSQ.js.map → chunk-RTFH2QR2.js.map} +1 -1
- package/dist/{chunk-5KWQMCH7.js → chunk-RZ5R6GDJ.js} +5 -5
- package/dist/{chunk-5KWQMCH7.js.map → chunk-RZ5R6GDJ.js.map} +1 -1
- package/dist/{chunk-MDCI4RSG.js → chunk-RZL4TOK3.js} +20 -6
- package/dist/chunk-RZL4TOK3.js.map +1 -0
- package/dist/{chunk-LMADWO4Q.js → chunk-SBAUZ7XH.js} +4 -4
- package/dist/{chunk-LMADWO4Q.js.map → chunk-SBAUZ7XH.js.map} +1 -1
- package/dist/{chunk-MPRWVZXF.js → chunk-SKG6G4HO.js} +108 -44
- package/dist/chunk-SKG6G4HO.js.map +1 -0
- package/dist/{chunk-HQAPUPBE.js → chunk-V2VDTQ7A.js} +3 -3
- package/dist/{chunk-HQAPUPBE.js.map → chunk-V2VDTQ7A.js.map} +1 -1
- package/dist/{chunk-GNUA7XT2.js → chunk-VS6XC5JE.js} +3 -3
- package/dist/{chunk-GNUA7XT2.js.map → chunk-VS6XC5JE.js.map} +1 -1
- package/dist/{chunk-5J6JWGY4.js → chunk-WDG5IVEA.js} +3 -3
- package/dist/{chunk-5J6JWGY4.js.map → chunk-WDG5IVEA.js.map} +1 -1
- package/dist/{chunk-M4G2SVXI.js → chunk-XWCV2A2F.js} +4 -4
- package/dist/{chunk-M4G2SVXI.js.map → chunk-XWCV2A2F.js.map} +1 -1
- package/dist/{chunk-UWD7YBDR.js → chunk-ZLDAPZIX.js} +4 -4
- package/dist/{chunk-UWD7YBDR.js.map → chunk-ZLDAPZIX.js.map} +1 -1
- package/dist/{claude-code-YRJIGXTL.js → claude-code-CMFGCDMX.js} +5 -5
- package/dist/{claude-code-YRJIGXTL.js.map → claude-code-CMFGCDMX.js.map} +1 -1
- package/dist/cli/index.js +322 -196
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/src/commands/asset-cmd.d.ts.map +1 -1
- package/dist/cli/src/commands/catalog.d.ts.map +1 -1
- package/dist/cli/src/commands/config.d.ts.map +1 -1
- package/dist/cli/src/commands/connector.d.ts.map +1 -1
- package/dist/cli/src/commands/harness-preflight.d.ts +3 -1
- package/dist/cli/src/commands/harness-preflight.d.ts.map +1 -1
- package/dist/cli/src/commands/harness.d.ts.map +1 -1
- package/dist/cli/src/commands/library-cmd.d.ts.map +1 -1
- package/dist/cli/src/commands/library-index.d.ts +3 -3
- package/dist/cli/src/commands/library-index.d.ts.map +1 -1
- package/dist/cli/src/commands/migrate-files.d.ts +8 -0
- package/dist/cli/src/commands/migrate-files.d.ts.map +1 -1
- package/dist/cli/src/commands/migrate-harness.d.ts.map +1 -1
- package/dist/cli/src/commands/migrate-providers.d.ts.map +1 -1
- package/dist/cli/src/commands/migrate.d.ts.map +1 -1
- package/dist/cli/src/commands/outdated.d.ts.map +1 -1
- package/dist/cli/src/commands/project.d.ts.map +1 -1
- package/dist/cli/src/commands/run.d.ts.map +1 -1
- package/dist/cli/src/commands/serve-preflight.d.ts +7 -5
- package/dist/cli/src/commands/serve-preflight.d.ts.map +1 -1
- package/dist/cli/src/commands/serve.d.ts.map +1 -1
- package/dist/cli/src/commands/source.d.ts.map +1 -1
- package/dist/cli/src/commands/validate.d.ts.map +1 -1
- package/dist/cli/src/config-checks.d.ts +50 -12
- package/dist/cli/src/config-checks.d.ts.map +1 -1
- package/dist/cli/src/ensure-sources.d.ts.map +1 -1
- package/dist/cli/src/harness-error.d.ts +7 -0
- package/dist/cli/src/harness-error.d.ts.map +1 -1
- package/dist/cli/src/helpers.d.ts.map +1 -1
- package/dist/connectors/config.js +5 -5
- package/dist/connectors/index.js +20 -19
- package/dist/connectors/src/index.d.ts +1 -1
- package/dist/connectors/src/index.d.ts.map +1 -1
- package/dist/connectors/src/managed-gitconfig.d.ts +12 -0
- package/dist/connectors/src/managed-gitconfig.d.ts.map +1 -1
- package/dist/{connectors-JKT32HZT.js → connectors-P5M2D4MH.js} +23 -22
- package/dist/{connectors-JKT32HZT.js.map → connectors-P5M2D4MH.js.map} +1 -1
- package/dist/core/index.js +7 -7
- package/dist/core/logging.js +1 -1
- package/dist/core/manifest.js +2 -2
- package/dist/core/models.js +1 -1
- package/dist/core/publish-manifest.js +3 -3
- package/dist/core/ref.js +1 -1
- package/dist/core/runtime-assets.js +5 -5
- package/dist/core/src/index.d.ts +2 -2
- package/dist/core/src/index.d.ts.map +1 -1
- package/dist/core/src/lock.d.ts +7 -1
- package/dist/core/src/lock.d.ts.map +1 -1
- package/dist/core/src/models.d.ts.map +1 -1
- package/dist/core/src/repo-manager.d.ts +28 -0
- package/dist/core/src/repo-manager.d.ts.map +1 -1
- package/dist/core/src/run-state-ignore.d.ts +5 -3
- package/dist/core/src/run-state-ignore.d.ts.map +1 -1
- package/dist/core/src/workspace-config.d.ts.map +1 -1
- package/dist/core/workspace-config.js +4 -4
- package/dist/deploy/index.js +7 -7
- package/dist/discovery/index.js +4 -4
- package/dist/{ensure-sources-GUJODKY5.js → ensure-sources-MOGLBEUF.js} +31 -29
- package/dist/ensure-sources-MOGLBEUF.js.map +1 -0
- package/dist/factory-assets/connectors/box.js +20 -19
- package/dist/factory-assets/connectors/deploy.js +20 -19
- package/dist/factory-assets/connectors/devserver.js +20 -19
- package/dist/factory-assets/connectors/flow/adapter.js +20 -19
- package/dist/factory-assets/connectors/flow/engine.js +8 -8
- package/dist/factory-assets/connectors/flow/run-flow.js +22 -21
- package/dist/factory-assets/connectors/flow.js +20 -19
- package/dist/factory-assets/connectors/git/exec.d.ts +7 -0
- package/dist/factory-assets/connectors/git/exec.d.ts.map +1 -1
- package/dist/factory-assets/connectors/git/repo.d.ts.map +1 -1
- package/dist/factory-assets/connectors/git.js +20 -19
- package/dist/factory-assets/connectors/gmail/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/gmail.js +20 -19
- package/dist/factory-assets/connectors/googledrive.js +20 -19
- package/dist/factory-assets/connectors/local.js +20 -19
- package/dist/factory-assets/connectors/mattermost.js +20 -19
- package/dist/factory-assets/connectors/memory.js +20 -19
- package/dist/factory-assets/connectors/minio/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/minio.js +20 -19
- package/dist/factory-assets/connectors/postgres/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/postgres.js +20 -19
- package/dist/factory-assets/connectors/s3/driver.d.ts.map +1 -1
- package/dist/factory-assets/connectors/s3.js +20 -19
- package/dist/factory-assets/connectors/sharepoint.js +20 -19
- package/dist/factory-assets/connectors/sqlite/adapter.d.ts.map +1 -1
- package/dist/factory-assets/connectors/sqlite.js +20 -19
- package/dist/factory-assets/connectors/static-server.js +20 -19
- package/dist/factory-assets/connectors/tunnel.js +20 -19
- package/dist/factory-assets/connectors/webdav.js +20 -19
- package/dist/factory-assets/connectors/xstate-store.js +20 -19
- package/dist/factory-assets/connectors/xstate.js +20 -19
- package/dist/{flows-UCP6ZUYS.js → flows-J64L5MGJ.js} +12 -12
- package/dist/{flows-UCP6ZUYS.js.map → flows-J64L5MGJ.js.map} +1 -1
- package/dist/{helpers-SO53MRHF.js → helpers-JL5AEGDT.js} +28 -26
- package/dist/{helpers-SO53MRHF.js.map → helpers-JL5AEGDT.js.map} +1 -1
- package/dist/library/index.js +10 -10
- package/dist/library/install.js +4 -4
- package/dist/library/src/index.d.ts +2 -2
- package/dist/library/src/index.d.ts.map +1 -1
- package/dist/library/src/library.d.ts +6 -2
- package/dist/library/src/library.d.ts.map +1 -1
- package/dist/library/src/local/db.d.ts.map +1 -1
- package/dist/library/src/local/library.d.ts +10 -3
- package/dist/library/src/local/library.d.ts.map +1 -1
- package/dist/library/src/local/local-catalog-source.d.ts +0 -9
- package/dist/library/src/local/local-catalog-source.d.ts.map +1 -1
- package/dist/library/src/local/schema.d.ts +27 -20
- package/dist/library/src/local/schema.d.ts.map +1 -1
- package/dist/library/src/manifest-writer.d.ts +13 -0
- package/dist/library/src/manifest-writer.d.ts.map +1 -1
- package/dist/library/src/remote/index.d.ts +1 -1
- package/dist/library/src/remote/index.d.ts.map +1 -1
- package/dist/library/src/remote/remote-catalog-source.d.ts +6 -0
- package/dist/library/src/remote/remote-catalog-source.d.ts.map +1 -1
- package/dist/library/src/sync/git-driver.d.ts.map +1 -1
- package/dist/{library-publish-Z5E3YZCZ.js → library-publish-DZNNZNMB.js} +49 -50
- package/dist/library-publish-DZNNZNMB.js.map +1 -0
- package/dist/model-endpoint/index.js +10 -10
- package/dist/open-library-OJREQ22B.js +61 -0
- package/dist/{open-library-A5OB72FD.js.map → open-library-OJREQ22B.js.map} +1 -1
- package/dist/{open-registry-BHBHQMBA.js → open-registry-OEU6QUK2.js} +14 -14
- package/dist/{open-registry-BHBHQMBA.js.map → open-registry-OEU6QUK2.js.map} +1 -1
- package/dist/paths-PXBWY532.js +22 -0
- package/dist/{paths-77PXOXZY.js.map → paths-PXBWY532.js.map} +1 -1
- package/dist/{plugin-store-XQP2CRYH.js → plugin-store-ZC3EGZ3D.js} +10 -10
- package/dist/{plugin-store-XQP2CRYH.js.map → plugin-store-ZC3EGZ3D.js.map} +1 -1
- package/dist/{project-KTEUN7KR.js → project-52UABBD5.js} +45 -46
- package/dist/{project-KTEUN7KR.js.map → project-52UABBD5.js.map} +1 -1
- package/dist/resolver/index.js +8 -8
- package/dist/runner/index.js +35 -35
- package/dist/runner/src/agent-loop.d.ts.map +1 -1
- package/dist/runner/src/serve.d.ts.map +1 -1
- package/dist/runner/src/turn-end-contract.d.ts.map +1 -1
- package/dist/runner/src/turn-scheduler.d.ts +8 -0
- package/dist/runner/src/turn-scheduler.d.ts.map +1 -1
- package/dist/sdk/asset-manager.js +14 -14
- package/dist/sdk/bridge.js +17 -16
- package/dist/sdk/core.js +7 -7
- package/dist/sdk/flow.js +8 -8
- package/dist/sdk/index.js +35 -35
- package/dist/sdk/resolver.js +8 -8
- package/dist/sdk/runner.js +35 -35
- package/dist/sdk/transport/ws/server.js +8 -8
- package/dist/sdk/transport/ws.js +8 -8
- package/dist/sdk/transport.js +8 -8
- package/dist/{serve-preflight-ODQGVUFL.js → serve-preflight-HOQ7CGYR.js} +30 -30
- package/dist/{serve-preflight-ODQGVUFL.js.map → serve-preflight-HOQ7CGYR.js.map} +1 -1
- package/dist/{setup-QJDI5YAW.js → setup-JIDZ372C.js} +29 -27
- package/dist/setup-JIDZ372C.js.map +1 -0
- package/dist/{skills-DE2BDQ6G.js → skills-5D4KDVPJ.js} +13 -13
- package/dist/{skills-DE2BDQ6G.js.map → skills-5D4KDVPJ.js.map} +1 -1
- package/dist/{source-FKOUYMEM.js → source-P6ZFSSZZ.js} +47 -46
- package/dist/{source-FKOUYMEM.js.map → source-P6ZFSSZZ.js.map} +1 -1
- package/dist/{source-manifest-publish-JA4TY4Z6.js → source-manifest-publish-Y36FHVDS.js} +49 -48
- package/dist/source-manifest-publish-Y36FHVDS.js.map +1 -0
- package/dist/transport/index.js +8 -8
- package/dist/transport/src/ws/server.d.ts.map +1 -1
- package/dist/transport/ws/server.js +8 -8
- package/dist/transport/ws.js +8 -8
- package/dist/tui/index.js +35 -35
- package/dist/workspace-plugin/adapters/mcp.js +27 -26
- package/dist/workspace-plugin/adapters/omp.js +14 -14
- package/dist/workspace-plugin/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-23Q5LT4U.js.map +0 -1
- package/dist/chunk-4KPQ6ILH.js.map +0 -1
- package/dist/chunk-5NTB64PW.js.map +0 -1
- package/dist/chunk-AVRTZAH2.js.map +0 -1
- package/dist/chunk-B2DGCYVC.js.map +0 -1
- package/dist/chunk-FZO64PLQ.js.map +0 -1
- package/dist/chunk-GYBDWW6K.js +0 -160
- package/dist/chunk-GYBDWW6K.js.map +0 -1
- package/dist/chunk-GYPPVANR.js +0 -357
- package/dist/chunk-GYPPVANR.js.map +0 -1
- package/dist/chunk-GZL6RN4Z.js.map +0 -1
- package/dist/chunk-JY2JXCL6.js +0 -334
- package/dist/chunk-JY2JXCL6.js.map +0 -1
- package/dist/chunk-KNY356I3.js.map +0 -1
- package/dist/chunk-LOO5CB5M.js +0 -12
- package/dist/chunk-LOO5CB5M.js.map +0 -1
- package/dist/chunk-MDCI4RSG.js.map +0 -1
- package/dist/chunk-MPRWVZXF.js.map +0 -1
- package/dist/chunk-O3FYOS64.js.map +0 -1
- package/dist/chunk-U3FI4DVT.js.map +0 -1
- package/dist/chunk-V3EJYOP2.js +0 -299
- package/dist/chunk-V3EJYOP2.js.map +0 -1
- package/dist/chunk-V5KG6CCV.js.map +0 -1
- package/dist/chunk-WPLZGT5Z.js.map +0 -1
- package/dist/chunk-X2B5GMJO.js.map +0 -1
- package/dist/chunk-Y2BQWZXT.js.map +0 -1
- package/dist/ensure-sources-GUJODKY5.js.map +0 -1
- package/dist/library-publish-Z5E3YZCZ.js.map +0 -1
- package/dist/open-library-A5OB72FD.js +0 -59
- package/dist/paths-77PXOXZY.js +0 -22
- package/dist/setup-QJDI5YAW.js.map +0 -1
- package/dist/source-manifest-publish-JA4TY4Z6.js.map +0 -1
|
@@ -0,0 +1,2443 @@
|
|
|
1
|
+
import { CodexNativeStateUnavailable, CodexAppServerClient, CODEX_NATIVE_STATE_FORMAT } from './chunk-5KSGZP3E.js';
|
|
2
|
+
import { freshBudget, decide, spend } from './chunk-7EKQDY2T.js';
|
|
3
|
+
import { fetchProviderModels } from './chunk-3AIEJSQR.js';
|
|
4
|
+
import { ReconfigureLedger, stripRunnerPrivateEnv, classifyGenericErrorMessage, RUNNER_AGENT_ENV_KEYS } from './chunk-5DE3DGVR.js';
|
|
5
|
+
import { AgentHarness, TOOL_ALLOWLIST_UNSUPPORTED, getBridgeLogger, registryHarnessConfig } from './chunk-NFFBYG2W.js';
|
|
6
|
+
import { BUILTIN_HARNESS_CATALOG, unresolvedProviderCredentials, registryCredentialEnv, entryCredentialValue, providerCredentialEnvName } from './chunk-KE5HDORU.js';
|
|
7
|
+
import { getProviderPreset } from './chunk-6UP2EDFN.js';
|
|
8
|
+
import { loadHarnessSdk } from './chunk-MEU2HHGV.js';
|
|
9
|
+
import { globalSkaileDir } from './chunk-L5VSIPQD.js';
|
|
10
|
+
import { registryEntries, parseModelRef, resolveEntryBaseUrl, isEntryEnabled, parseCredentialRef } from './chunk-RXGPFAMN.js';
|
|
11
|
+
import { openSqlite } from './chunk-RZL4TOK3.js';
|
|
12
|
+
import { join, isAbsolute, dirname, relative, resolve } from 'path';
|
|
13
|
+
import { isDeepStrictEqual } from 'util';
|
|
14
|
+
import * as z from 'zod';
|
|
15
|
+
import { randomUUID, createHash } from 'crypto';
|
|
16
|
+
import { lstatSync, rmSync, existsSync, readFileSync, mkdirSync, writeFileSync, realpathSync, chmodSync, renameSync, readdirSync, statSync, symlinkSync, copyFileSync } from 'fs';
|
|
17
|
+
import { stringify } from 'smol-toml';
|
|
18
|
+
import { homedir } from 'os';
|
|
19
|
+
|
|
20
|
+
var identifier = z.string().min(1).max(4096);
|
|
21
|
+
var timestamp = z.iso.datetime({ offset: true });
|
|
22
|
+
var common = {
|
|
23
|
+
configId: identifier,
|
|
24
|
+
credentialVersion: z.number().int().nonnegative().safe(),
|
|
25
|
+
mintedAt: timestamp
|
|
26
|
+
};
|
|
27
|
+
var subscription = z.strictObject({
|
|
28
|
+
...common,
|
|
29
|
+
kind: z.literal("codex-subscription"),
|
|
30
|
+
accessToken: z.string().min(1).max(65536),
|
|
31
|
+
chatgptAccountId: identifier,
|
|
32
|
+
chatgptPlanType: identifier.optional(),
|
|
33
|
+
expiresAt: timestamp
|
|
34
|
+
});
|
|
35
|
+
var delivery = z.discriminatedUnion("kind", [
|
|
36
|
+
subscription,
|
|
37
|
+
z.strictObject({
|
|
38
|
+
...common,
|
|
39
|
+
kind: z.literal("openai-api-key"),
|
|
40
|
+
apiKey: z.string().min(1).max(65536)
|
|
41
|
+
})
|
|
42
|
+
]);
|
|
43
|
+
function parseCodexCredential(value, now = Date.now()) {
|
|
44
|
+
const parsed = delivery.safeParse(value);
|
|
45
|
+
if (!parsed.success) throw new Error("Invalid managed AI credential");
|
|
46
|
+
const credential = parsed.data;
|
|
47
|
+
const mintedAt = Date.parse(credential.mintedAt);
|
|
48
|
+
if (mintedAt > now + 6e4 || credential.kind === "codex-subscription" && (Date.parse(credential.expiresAt) <= now || Date.parse(credential.expiresAt) <= mintedAt))
|
|
49
|
+
throw new Error("Invalid managed AI credential timestamps");
|
|
50
|
+
return credential;
|
|
51
|
+
}
|
|
52
|
+
function parseCodexRefreshInput(value, pinned) {
|
|
53
|
+
const parsed = z.strictObject({
|
|
54
|
+
kind: z.literal("codex-subscription"),
|
|
55
|
+
configId: identifier,
|
|
56
|
+
credentialVersion: common.credentialVersion,
|
|
57
|
+
reason: z.enum(["initial", "refresh", "retry-401"])
|
|
58
|
+
}).safeParse(value);
|
|
59
|
+
if (!parsed.success || parsed.data.configId !== pinned.configId || parsed.data.credentialVersion !== pinned.credentialVersion)
|
|
60
|
+
throw new Error("Invalid managed AI refresh binding");
|
|
61
|
+
return parsed.data;
|
|
62
|
+
}
|
|
63
|
+
function parseCodexRefreshResult(value, pinned, now = Date.now()) {
|
|
64
|
+
const parsed = z.discriminatedUnion("ok", [
|
|
65
|
+
z.strictObject({ ok: z.literal(true), credential: subscription }),
|
|
66
|
+
z.strictObject({
|
|
67
|
+
ok: z.literal(false),
|
|
68
|
+
code: z.enum(["not-configured", "revoked", "provider-error", "backend-error"]),
|
|
69
|
+
message: z.string()
|
|
70
|
+
})
|
|
71
|
+
]).safeParse(value);
|
|
72
|
+
if (!parsed.success) throw new Error("Invalid managed AI refresh result");
|
|
73
|
+
if (!parsed.data.ok) return { ...parsed.data, message: "Managed subscription refresh failed" };
|
|
74
|
+
const credential = parseCodexCredential(parsed.data.credential, now);
|
|
75
|
+
if (credential.kind !== "codex-subscription" || credential.configId !== pinned.configId || credential.chatgptAccountId !== pinned.chatgptAccountId || credential.credentialVersion < pinned.credentialVersion)
|
|
76
|
+
throw new Error("Invalid managed AI refresh binding");
|
|
77
|
+
return { ok: true, credential };
|
|
78
|
+
}
|
|
79
|
+
function managedCodexRegistryViolation(registry) {
|
|
80
|
+
if (!registry) return void 0;
|
|
81
|
+
const entries = registryEntries(registry);
|
|
82
|
+
if (entries.length === 0) return void 0;
|
|
83
|
+
if (entries.length > 1)
|
|
84
|
+
return `registry carries ${entries.length} entries; managed codex admits exactly one`;
|
|
85
|
+
const [id, entry] = entries[0];
|
|
86
|
+
if (!isEntryEnabled(entry)) return `entry '${id}' is disabled`;
|
|
87
|
+
if (entry.kind !== "llm") return `entry '${id}' is a '${entry.kind}' endpoint, not an llm one`;
|
|
88
|
+
const surface = entry.surface;
|
|
89
|
+
if (surface !== "openai-codex-responses")
|
|
90
|
+
return `entry '${id}' declares surface '${surface ?? "none"}'; managed codex admits only openai-codex-responses`;
|
|
91
|
+
const credential = entry.credential;
|
|
92
|
+
if (credential?.kind !== "oauth")
|
|
93
|
+
return `entry '${id}' carries a '${credential?.kind ?? "none"}' credential; managed codex admits only a platform-injected oauth credential`;
|
|
94
|
+
const ref = credential.ref ? parseCredentialRef(credential.ref) : void 0;
|
|
95
|
+
if (!ref || ref.form !== "platform")
|
|
96
|
+
return `entry '${id}' resolves its credential outside the control plane; managed codex admits only a 'platform:' reference`;
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
var invalid = () => new Error("AI_PROFILE_INVALID");
|
|
100
|
+
var within = (path, root) => {
|
|
101
|
+
const rel = relative(root, path);
|
|
102
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
103
|
+
};
|
|
104
|
+
function directory(path) {
|
|
105
|
+
if (!existsSync(path)) mkdirSync(path, { mode: 448 });
|
|
106
|
+
const stat = lstatSync(path);
|
|
107
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || stat.mode & 63) throw invalid();
|
|
108
|
+
}
|
|
109
|
+
function readRegular(path, limit2) {
|
|
110
|
+
const stat = lstatSync(path);
|
|
111
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > limit2) throw invalid();
|
|
112
|
+
return readFileSync(path);
|
|
113
|
+
}
|
|
114
|
+
function prepareCodexInstance(config, options, credential) {
|
|
115
|
+
if (!isAbsolute(options.privateDirectory) || !isAbsolute(config.cwd) || !config.sessionId)
|
|
116
|
+
throw invalid();
|
|
117
|
+
directory(options.privateDirectory);
|
|
118
|
+
const root = realpathSync(options.privateDirectory);
|
|
119
|
+
const cwd = realpathSync(config.cwd);
|
|
120
|
+
if (within(root, cwd) || within(cwd, root)) throw invalid();
|
|
121
|
+
const lock = join(root, ".active");
|
|
122
|
+
const leasePath = join(root, "lease.db");
|
|
123
|
+
if (existsSync(leasePath) && !lstatSync(leasePath).isFile()) throw invalid();
|
|
124
|
+
const lease = openSqlite(leasePath);
|
|
125
|
+
try {
|
|
126
|
+
lease.exec("PRAGMA busy_timeout=0; BEGIN EXCLUSIVE");
|
|
127
|
+
chmodSync(leasePath, 384);
|
|
128
|
+
rmSync(lock, { force: true });
|
|
129
|
+
writeFileSync(lock, "active", { flag: "wx", mode: 384 });
|
|
130
|
+
} catch (error) {
|
|
131
|
+
lease.close();
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
let released = false;
|
|
135
|
+
const release = () => {
|
|
136
|
+
if (released) return;
|
|
137
|
+
released = true;
|
|
138
|
+
try {
|
|
139
|
+
rmSync(lock, { force: true });
|
|
140
|
+
} finally {
|
|
141
|
+
lease.close();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
try {
|
|
145
|
+
const key = createHash("sha256").update(
|
|
146
|
+
JSON.stringify({
|
|
147
|
+
harness: "codex-app-server-v1",
|
|
148
|
+
session: config.sessionId,
|
|
149
|
+
config: credential.configId,
|
|
150
|
+
kind: credential.kind,
|
|
151
|
+
account: credential.kind === "codex-subscription" ? credential.chatgptAccountId : null
|
|
152
|
+
})
|
|
153
|
+
).digest("hex");
|
|
154
|
+
const slots = join(root, CODEX_NATIVE_STATE_FORMAT);
|
|
155
|
+
directory(slots);
|
|
156
|
+
const stateRoot = join(slots, key);
|
|
157
|
+
directory(stateRoot);
|
|
158
|
+
for (const name of ["instructions.new", "binding.new"])
|
|
159
|
+
rmSync(join(stateRoot, name), { force: true });
|
|
160
|
+
const bindingPath = join(stateRoot, "binding.json");
|
|
161
|
+
if (config.resumeSessionId && !existsSync(bindingPath)) throw new CodexNativeStateUnavailable();
|
|
162
|
+
if (existsSync(bindingPath)) {
|
|
163
|
+
const bytes = readRegular(bindingPath, 4096);
|
|
164
|
+
if (config.resumeSessionId) {
|
|
165
|
+
let binding;
|
|
166
|
+
try {
|
|
167
|
+
binding = JSON.parse(bytes.toString("utf8"));
|
|
168
|
+
} catch {
|
|
169
|
+
throw new CodexNativeStateUnavailable();
|
|
170
|
+
}
|
|
171
|
+
if (!binding || typeof binding !== "object" || Array.isArray(binding) || Object.keys(binding).sort().join(",") !== "format,key,threadId" || binding.format !== CODEX_NATIVE_STATE_FORMAT || binding.key !== key || binding.threadId !== config.resumeSessionId)
|
|
172
|
+
throw new CodexNativeStateUnavailable();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const home = join(stateRoot, "home");
|
|
176
|
+
if (config.resumeSessionId && !existsSync(home)) throw new CodexNativeStateUnavailable();
|
|
177
|
+
directory(home);
|
|
178
|
+
for (const subdir of ["config", "cache", "data", "tmp"]) directory(join(home, subdir));
|
|
179
|
+
if (["auth.json", ".credentials.json"].some((name) => existsSync(join(home, name))))
|
|
180
|
+
throw invalid();
|
|
181
|
+
const instructionFile = join(stateRoot, "instructions.md");
|
|
182
|
+
const instructions = [
|
|
183
|
+
...(options.instructionFiles ?? []).map(
|
|
184
|
+
(path) => readRegular(path, 1024 * 1024).toString("utf8")
|
|
185
|
+
),
|
|
186
|
+
...config.systemPrompt ? [config.systemPrompt] : []
|
|
187
|
+
].join("\n\n---\n\n");
|
|
188
|
+
const temporary = join(stateRoot, "instructions.new");
|
|
189
|
+
writeFileSync(temporary, instructions, { mode: 384, flag: "wx" });
|
|
190
|
+
renameSync(temporary, instructionFile);
|
|
191
|
+
const target = join(home, "skills");
|
|
192
|
+
if (!options.skillsDirectory && existsSync(target)) rmSync(target, { recursive: true });
|
|
193
|
+
if (options.skillsDirectory) {
|
|
194
|
+
const source = realpathSync(options.skillsDirectory);
|
|
195
|
+
if (within(target, source) || within(source, target)) throw invalid();
|
|
196
|
+
if (existsSync(target)) rmSync(target, { recursive: true });
|
|
197
|
+
let files = 0;
|
|
198
|
+
let entries = 0;
|
|
199
|
+
let bytes = 0;
|
|
200
|
+
const copy = (source2, destination) => {
|
|
201
|
+
if (++entries > 1024) throw invalid();
|
|
202
|
+
if (lstatSync(source2).isSymbolicLink()) throw invalid();
|
|
203
|
+
mkdirSync(destination, { mode: 448 });
|
|
204
|
+
for (const entry of readdirSync(source2, { withFileTypes: true })) {
|
|
205
|
+
const src = join(source2, entry.name);
|
|
206
|
+
const dst = join(destination, entry.name);
|
|
207
|
+
if (entry.isSymbolicLink()) throw invalid();
|
|
208
|
+
if (entry.isDirectory()) copy(src, dst);
|
|
209
|
+
else {
|
|
210
|
+
const content = readRegular(src, 1024 * 1024);
|
|
211
|
+
bytes += content.byteLength;
|
|
212
|
+
if (++files > 512 || bytes > 8 * 1024 * 1024) throw invalid();
|
|
213
|
+
writeFileSync(dst, content, { mode: 384, flag: "wx" });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
copy(options.skillsDirectory, target);
|
|
218
|
+
}
|
|
219
|
+
const bindThread = (threadId) => {
|
|
220
|
+
if (!threadId || threadId.length > 4096) throw invalid();
|
|
221
|
+
const next = join(stateRoot, "binding.new");
|
|
222
|
+
writeFileSync(next, JSON.stringify({ format: CODEX_NATIVE_STATE_FORMAT, key, threadId }), {
|
|
223
|
+
mode: 384,
|
|
224
|
+
flag: "wx"
|
|
225
|
+
});
|
|
226
|
+
renameSync(next, bindingPath);
|
|
227
|
+
};
|
|
228
|
+
const setToolInstructions = (text2) => {
|
|
229
|
+
writeFileSync(temporary, `${instructions}
|
|
230
|
+
|
|
231
|
+
${text2}`, { mode: 384, flag: "wx" });
|
|
232
|
+
renameSync(temporary, instructionFile);
|
|
233
|
+
};
|
|
234
|
+
return {
|
|
235
|
+
root,
|
|
236
|
+
stateRoot,
|
|
237
|
+
cwd,
|
|
238
|
+
home,
|
|
239
|
+
instructionFile,
|
|
240
|
+
bindThread,
|
|
241
|
+
release,
|
|
242
|
+
setToolInstructions
|
|
243
|
+
};
|
|
244
|
+
} catch (error) {
|
|
245
|
+
release();
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function codexInstanceEnvironment(input, home) {
|
|
250
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
251
|
+
"PATH",
|
|
252
|
+
"LANG",
|
|
253
|
+
"LC_ALL",
|
|
254
|
+
"TZ",
|
|
255
|
+
"SSL_CERT_FILE",
|
|
256
|
+
"SSL_CERT_DIR",
|
|
257
|
+
"HTTPS_PROXY",
|
|
258
|
+
"HTTP_PROXY",
|
|
259
|
+
"NO_PROXY"
|
|
260
|
+
]);
|
|
261
|
+
if (!input || Object.entries(input).some(([key, value]) => !allowed.has(key) || typeof value !== "string"))
|
|
262
|
+
throw invalid();
|
|
263
|
+
return {
|
|
264
|
+
...input,
|
|
265
|
+
HOME: home,
|
|
266
|
+
CODEX_HOME: home,
|
|
267
|
+
XDG_CONFIG_HOME: join(home, "config"),
|
|
268
|
+
XDG_CACHE_HOME: join(home, "cache"),
|
|
269
|
+
XDG_DATA_HOME: join(home, "data"),
|
|
270
|
+
TMPDIR: join(home, "tmp")
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
var invalid2 = () => new Error("CODEX_INPUT_INVALID");
|
|
274
|
+
var inside = (root, path) => {
|
|
275
|
+
const rel = relative(root, path);
|
|
276
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
277
|
+
};
|
|
278
|
+
function codexPromptInput(input, cwd, skills) {
|
|
279
|
+
if (!input || typeof input.text !== "string" || input.text.length > 1024 * 1024 || input.attachments !== void 0 && !Array.isArray(input.attachments) || (input.attachments?.length ?? 0) > 32)
|
|
280
|
+
throw invalid2();
|
|
281
|
+
const result = [
|
|
282
|
+
{ type: "text", text: input.text }
|
|
283
|
+
];
|
|
284
|
+
for (const attachment of input.attachments ?? []) {
|
|
285
|
+
try {
|
|
286
|
+
if (!attachment || typeof attachment.path !== "string") throw invalid2();
|
|
287
|
+
const root = attachment.type === "skill" && skills ? realpathSync(skills.source) : realpathSync(cwd);
|
|
288
|
+
const path = realpathSync(
|
|
289
|
+
isAbsolute(attachment.path) ? attachment.path : join(root, attachment.path)
|
|
290
|
+
);
|
|
291
|
+
if (!inside(root, path) || !statSync(path).isFile()) throw invalid2();
|
|
292
|
+
if (attachment.type === "image") result.push({ type: "localImage", path });
|
|
293
|
+
else if (attachment.type === "file" || attachment.type === "skill") {
|
|
294
|
+
if (!attachment.name || [...attachment.name].some((char) => char.charCodeAt(0) < 32))
|
|
295
|
+
throw invalid2();
|
|
296
|
+
if (attachment.type === "skill") {
|
|
297
|
+
if (!skills) throw invalid2();
|
|
298
|
+
const copied = realpathSync(join(skills.target, relative(root, path)));
|
|
299
|
+
if (!inside(realpathSync(skills.target), copied) || !statSync(copied).isFile())
|
|
300
|
+
throw invalid2();
|
|
301
|
+
result.push({ type: "skill", name: attachment.name, path: copied });
|
|
302
|
+
} else result.push({ type: "mention", name: attachment.name, path });
|
|
303
|
+
} else throw invalid2();
|
|
304
|
+
} catch {
|
|
305
|
+
throw invalid2();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
var invalid3 = () => new Error("AI_PROFILE_INVALID");
|
|
311
|
+
var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
312
|
+
var within2 = (child, parent) => {
|
|
313
|
+
const rel = relative(parent, child);
|
|
314
|
+
return rel === "" || !rel.startsWith("../") && rel !== ".." && !isAbsolute(rel);
|
|
315
|
+
};
|
|
316
|
+
var hasControl = (path) => [...path].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127);
|
|
317
|
+
var directory2 = (path) => {
|
|
318
|
+
if (!isAbsolute(path) || hasControl(path)) throw invalid3();
|
|
319
|
+
const stat = lstatSync(path);
|
|
320
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw invalid3();
|
|
321
|
+
return realpathSync(path);
|
|
322
|
+
};
|
|
323
|
+
function prepareNativePolicy(input) {
|
|
324
|
+
const cwd = directory2(input.cwd);
|
|
325
|
+
const privateRoot = directory2(input.privateRoot);
|
|
326
|
+
const privateDirectory = directory2(input.privateDirectory);
|
|
327
|
+
if (within2(cwd, privateRoot) || within2(privateRoot, cwd) || !within2(privateDirectory, privateRoot) || privateDirectory === privateRoot || input.sandboxMode !== "read-only" && input.sandboxMode !== "workspace-write")
|
|
328
|
+
throw invalid3();
|
|
329
|
+
const stat = lstatSync(privateRoot);
|
|
330
|
+
if (stat.mode & 63 || process.getuid && stat.uid !== 0 && stat.uid !== process.getuid())
|
|
331
|
+
throw invalid3();
|
|
332
|
+
const filesystem = Object.freeze({
|
|
333
|
+
[cwd]: input.sandboxMode === "workspace-write" ? "write" : "read",
|
|
334
|
+
[privateRoot]: "deny"
|
|
335
|
+
});
|
|
336
|
+
const config = Object.freeze({
|
|
337
|
+
default_permissions: "managed",
|
|
338
|
+
features: Object.freeze({ use_legacy_landlock: false }),
|
|
339
|
+
permissions: Object.freeze({
|
|
340
|
+
managed: Object.freeze({
|
|
341
|
+
extends: ":read-only",
|
|
342
|
+
filesystem,
|
|
343
|
+
network: Object.freeze({ enabled: false })
|
|
344
|
+
})
|
|
345
|
+
})
|
|
346
|
+
});
|
|
347
|
+
const profile = `{ managed = { extends = ":read-only", filesystem = { ${Object.entries(filesystem).map(([path, access]) => `${JSON.stringify(path)} = ${JSON.stringify(access)}`).join(", ")} }, network = { enabled = false } } }`;
|
|
348
|
+
const policy = Object.freeze({
|
|
349
|
+
config,
|
|
350
|
+
cliArgs: Object.freeze([
|
|
351
|
+
"-c",
|
|
352
|
+
'default_permissions="managed"',
|
|
353
|
+
"-c",
|
|
354
|
+
"features.use_legacy_landlock=false",
|
|
355
|
+
"-c",
|
|
356
|
+
`permissions=${profile}`
|
|
357
|
+
])
|
|
358
|
+
});
|
|
359
|
+
prepared.set(policy, { cwd, privateRoot, writable: input.sandboxMode === "workspace-write" });
|
|
360
|
+
return policy;
|
|
361
|
+
}
|
|
362
|
+
var prepared = /* @__PURE__ */ new WeakMap();
|
|
363
|
+
var active = /* @__PURE__ */ new WeakMap();
|
|
364
|
+
function activateNativeApprovalPolicy(policy, response, signal) {
|
|
365
|
+
const binding = prepared.get(policy);
|
|
366
|
+
if (!binding || signal.aborted || !record(response) || !record(response.thread) || typeof response.thread.id !== "string" || !response.thread.id || response.cwd !== binding.cwd || !record(response.activePermissionProfile) || response.activePermissionProfile.id !== "managed" || response.activePermissionProfile.extends !== ":read-only")
|
|
367
|
+
throw invalid3();
|
|
368
|
+
const token = Object.freeze({});
|
|
369
|
+
active.set(token, { ...binding, threadId: response.thread.id, signal });
|
|
370
|
+
return token;
|
|
371
|
+
}
|
|
372
|
+
function isNativeApprovalPolicyActive(policy, threadId) {
|
|
373
|
+
if (!record(policy)) return false;
|
|
374
|
+
const binding = active.get(policy);
|
|
375
|
+
return Boolean(binding && !binding.signal.aborted && binding.threadId === threadId);
|
|
376
|
+
}
|
|
377
|
+
function allowsNativeApprovalPath(policy, path, access) {
|
|
378
|
+
if (!policy) return false;
|
|
379
|
+
const binding = active.get(policy);
|
|
380
|
+
if (!binding || binding.signal.aborted || !isAbsolute(path) || hasControl(path) || access !== "read" && access !== "write" || access === "write" && !binding.writable)
|
|
381
|
+
return false;
|
|
382
|
+
try {
|
|
383
|
+
let existing = resolve(path);
|
|
384
|
+
const suffix = [];
|
|
385
|
+
while (true) {
|
|
386
|
+
try {
|
|
387
|
+
lstatSync(existing);
|
|
388
|
+
break;
|
|
389
|
+
} catch (error) {
|
|
390
|
+
if (error.code !== "ENOENT" || dirname(existing) === existing)
|
|
391
|
+
return false;
|
|
392
|
+
suffix.unshift(existing.slice(dirname(existing).length + 1));
|
|
393
|
+
existing = dirname(existing);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
const canonical = resolve(realpathSync(existing), ...suffix);
|
|
397
|
+
return within2(canonical, binding.cwd) && !within2(canonical, binding.privateRoot);
|
|
398
|
+
} catch {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// bridge/src/codex/native-approvals.ts
|
|
404
|
+
function createCodexNativeApprovalHandlers(options) {
|
|
405
|
+
const active2 = (signal, threadId) => !signal.aborted && isNativeApprovalPolicyActive(options.getPolicy(), threadId);
|
|
406
|
+
const text2 = (value) => typeof value === "string" && value.length > 0 && value.length <= 8192;
|
|
407
|
+
const scoped = async (path, access, signal, threadId) => active2(signal, threadId) && text2(path) && allowsNativeApprovalPath(options.getPolicy(), path, access) && active2(signal, threadId);
|
|
408
|
+
const permissions = async (value, signal, threadId) => {
|
|
409
|
+
if (value.network?.enabled || value.network != null && Object.keys(value.network).some((key) => key !== "enabled") || Object.keys(value).some((key) => !["network", "fileSystem"].includes(key)))
|
|
410
|
+
return false;
|
|
411
|
+
const fs = value.fileSystem;
|
|
412
|
+
if (!fs) return true;
|
|
413
|
+
if (fs.entries != null || fs.globScanMaxDepth != null || Object.keys(fs).some((key) => !["read", "write", "entries", "globScanMaxDepth"].includes(key)))
|
|
414
|
+
return false;
|
|
415
|
+
for (const access of ["read", "write"]) {
|
|
416
|
+
const paths = fs[access];
|
|
417
|
+
if (paths != null && (!Array.isArray(paths) || paths.length > 32)) return false;
|
|
418
|
+
for (const path of paths ?? [])
|
|
419
|
+
if (!await scoped(path, access, signal, threadId)) return false;
|
|
420
|
+
}
|
|
421
|
+
return active2(signal, threadId);
|
|
422
|
+
};
|
|
423
|
+
const consent = async (identity, approval, signal) => {
|
|
424
|
+
if (!active2(signal, identity.threadId) || approval.details.length > 64 || approval.details.some((detail) => detail.length > 8192) || approval.details.reduce((sum, detail) => sum + detail.length, 0) > 65536)
|
|
425
|
+
return false;
|
|
426
|
+
try {
|
|
427
|
+
return await options.questions.requestApproval(identity, approval, signal) && active2(signal, identity.threadId);
|
|
428
|
+
} catch {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
return {
|
|
433
|
+
"item/commandExecution/requestApproval": async (input, signal) => {
|
|
434
|
+
const params = structuredClone(input);
|
|
435
|
+
const allowed = async () => active2(signal, params.threadId) && text2(params.command) && (params.kind == null || params.kind === "command") && !params.environmentId && !params.networkApprovalContext && (params.availableDecisions == null || params.availableDecisions.includes("accept")) && await scoped(params.cwd, "read", signal, params.threadId) && (!params.additionalPermissions || await permissions(params.additionalPermissions, signal, params.threadId));
|
|
436
|
+
if (!await allowed()) return { decision: "decline" };
|
|
437
|
+
const details = [`Command: ${params.command}`, `Directory: ${params.cwd}`];
|
|
438
|
+
for (const access of ["read", "write"])
|
|
439
|
+
for (const path of params.additionalPermissions?.fileSystem?.[access] ?? [])
|
|
440
|
+
details.push(`${access}: ${path}`);
|
|
441
|
+
const accepted = await consent(params, { kind: "command", details }, signal);
|
|
442
|
+
return { decision: accepted && await allowed() ? "accept" : "decline" };
|
|
443
|
+
},
|
|
444
|
+
"item/fileChange/requestApproval": async (input, signal) => {
|
|
445
|
+
const params = structuredClone(input);
|
|
446
|
+
if (!active2(signal, params.threadId)) return { decision: "decline" };
|
|
447
|
+
const original = options.getFileChanges(params);
|
|
448
|
+
if (!original?.length || original.length > 16) return { decision: "decline" };
|
|
449
|
+
const changes = structuredClone(original);
|
|
450
|
+
const snapshot = JSON.stringify(changes);
|
|
451
|
+
const allowed = async () => {
|
|
452
|
+
if (!active2(signal, params.threadId) || snapshot !== JSON.stringify(options.getFileChanges(params)))
|
|
453
|
+
return false;
|
|
454
|
+
if (params.grantRoot != null && !await scoped(params.grantRoot, "write", signal, params.threadId))
|
|
455
|
+
return false;
|
|
456
|
+
for (const change of changes) {
|
|
457
|
+
if (!text2(change.diff) || !await scoped(change.path, "write", signal, params.threadId))
|
|
458
|
+
return false;
|
|
459
|
+
if (!["add", "delete", "update"].includes(change.kind.type)) return false;
|
|
460
|
+
if (change.kind.type === "update" && change.kind.move_path != null && !await scoped(change.kind.move_path, "write", signal, params.threadId))
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
return active2(signal, params.threadId);
|
|
464
|
+
};
|
|
465
|
+
if (!await allowed()) return { decision: "decline" };
|
|
466
|
+
const details = changes.flatMap((change) => [
|
|
467
|
+
`${change.kind.type}: ${change.path}${change.kind.type === "update" && change.kind.move_path ? ` \u2192 ${change.kind.move_path}` : ""}`,
|
|
468
|
+
change.diff
|
|
469
|
+
]);
|
|
470
|
+
const accepted = await consent(params, { kind: "file", details }, signal);
|
|
471
|
+
return { decision: accepted && await allowed() ? "accept" : "decline" };
|
|
472
|
+
},
|
|
473
|
+
"item/permissions/requestApproval": async (input, signal) => {
|
|
474
|
+
const params = structuredClone(input);
|
|
475
|
+
const allowed = async () => active2(signal, params.threadId) && !params.environmentId && await scoped(params.cwd, "read", signal, params.threadId) && await permissions(params.permissions, signal, params.threadId);
|
|
476
|
+
if (!await allowed()) return { permissions: {}, scope: "turn" };
|
|
477
|
+
const details = [`Directory: ${params.cwd}`];
|
|
478
|
+
for (const access of ["read", "write"])
|
|
479
|
+
for (const path of params.permissions.fileSystem?.[access] ?? [])
|
|
480
|
+
details.push(`${access}: ${path}`);
|
|
481
|
+
if (details.length === 1) return { permissions: {}, scope: "turn" };
|
|
482
|
+
const accepted = await consent(params, { kind: "permissions", details }, signal);
|
|
483
|
+
return {
|
|
484
|
+
permissions: accepted && await allowed() ? params.permissions : {},
|
|
485
|
+
scope: "turn"
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// bridge/src/codex/native-mcp-approval.ts
|
|
492
|
+
var record2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
493
|
+
var text = (value) => typeof value === "string" && value.length > 0 && Buffer.byteLength(value) <= 8192;
|
|
494
|
+
function createCodexMcpApprovalHandler(options) {
|
|
495
|
+
return async (input, signal) => {
|
|
496
|
+
const decline = { action: "decline", content: null, _meta: null };
|
|
497
|
+
try {
|
|
498
|
+
const params = structuredClone(input);
|
|
499
|
+
const active2 = () => !signal.aborted && options.bindingActive() && isNativeApprovalPolicyActive(options.getPolicy(), params.threadId);
|
|
500
|
+
if (!active2() || !text(options.serverName) || params.serverName !== options.serverName || !text(params.threadId) || !text(params.turnId) || params.mode !== "form" || !text(params.message) || Object.keys(params).some(
|
|
501
|
+
(key) => ![
|
|
502
|
+
"threadId",
|
|
503
|
+
"turnId",
|
|
504
|
+
"serverName",
|
|
505
|
+
"mode",
|
|
506
|
+
"message",
|
|
507
|
+
"requestedSchema",
|
|
508
|
+
"_meta"
|
|
509
|
+
].includes(key)
|
|
510
|
+
) || !record2(params._meta) || params._meta.codex_approval_kind !== "mcp_tool_call" || !record2(params.requestedSchema) || params.requestedSchema.type !== "object" || !record2(params.requestedSchema.properties) || Object.keys(params.requestedSchema.properties).length !== 0 || Object.keys(params.requestedSchema).some(
|
|
511
|
+
(key) => !["type", "properties", "required"].includes(key)
|
|
512
|
+
) || params.requestedSchema.required !== void 0 && (!Array.isArray(params.requestedSchema.required) || params.requestedSchema.required.length !== 0) || Buffer.byteLength(JSON.stringify(params._meta)) > 65536)
|
|
513
|
+
return decline;
|
|
514
|
+
const accepted = await options.questions.requestApproval(
|
|
515
|
+
void 0,
|
|
516
|
+
{
|
|
517
|
+
kind: "mcp",
|
|
518
|
+
details: [
|
|
519
|
+
`Configured MCP server: ${options.serverName}`,
|
|
520
|
+
params.message,
|
|
521
|
+
"This is native request presentation, not an independently verified tool identity. Tool permissions are checked again when dispatched."
|
|
522
|
+
]
|
|
523
|
+
},
|
|
524
|
+
signal
|
|
525
|
+
);
|
|
526
|
+
return accepted && active2() ? { action: "accept", content: null, _meta: null } : decline;
|
|
527
|
+
} catch {
|
|
528
|
+
return decline;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
var CodexQuestions = class {
|
|
533
|
+
constructor(emit) {
|
|
534
|
+
this.emit = emit;
|
|
535
|
+
}
|
|
536
|
+
emit;
|
|
537
|
+
pending;
|
|
538
|
+
request = async (params, signal) => this.park(params, signal);
|
|
539
|
+
/** Park native consent; MCP uses only local correlation because it supplies no native item ID. */
|
|
540
|
+
async requestApproval(identity, approval, signal) {
|
|
541
|
+
const result = await this.park(
|
|
542
|
+
{
|
|
543
|
+
...identity,
|
|
544
|
+
isBlocking: true,
|
|
545
|
+
questions: [
|
|
546
|
+
{
|
|
547
|
+
id: "native-approval",
|
|
548
|
+
header: "Approval",
|
|
549
|
+
question: approval.kind === "permissions" ? "Allow these permissions for the current turn?" : approval.kind === "mcp" ? "Allow this native MCP request once?" : "Allow this native action once?",
|
|
550
|
+
options: [
|
|
551
|
+
{
|
|
552
|
+
label: "Approve once",
|
|
553
|
+
description: approval.kind === "permissions" ? "Allow for the current turn." : "Allow only this request."
|
|
554
|
+
},
|
|
555
|
+
{ label: "Deny", description: "Do not allow this request." }
|
|
556
|
+
]
|
|
557
|
+
}
|
|
558
|
+
]
|
|
559
|
+
},
|
|
560
|
+
signal,
|
|
561
|
+
approval
|
|
562
|
+
);
|
|
563
|
+
return result.answers["native-approval"]?.answers[0] === "Approve once";
|
|
564
|
+
}
|
|
565
|
+
async park(params, signal, approval) {
|
|
566
|
+
signal.throwIfAborted();
|
|
567
|
+
if (this.pending || !params.isBlocking || !Array.isArray(params.questions) || params.questions.length === 0 || params.questions.length > 16 || params.questions.some((q) => !q.id || !q.question || q.isSecret) || new Set(params.questions.map((q) => q.id)).size !== params.questions.length || new Set(params.questions.map((q) => q.question)).size !== params.questions.length)
|
|
568
|
+
throw new Error("CODEX_INPUT_UNSUPPORTED");
|
|
569
|
+
params = structuredClone(params);
|
|
570
|
+
approval = approval ? structuredClone(approval) : void 0;
|
|
571
|
+
const id = randomUUID();
|
|
572
|
+
const eventId = params.itemId ?? `local-consent:${id}`;
|
|
573
|
+
return new Promise((resolve3, reject) => {
|
|
574
|
+
const abort = () => finish();
|
|
575
|
+
const finish = (result) => {
|
|
576
|
+
if (this.pending?.id !== id) return;
|
|
577
|
+
this.pending = void 0;
|
|
578
|
+
signal.removeEventListener("abort", abort);
|
|
579
|
+
if (result && !signal.aborted) resolve3(result);
|
|
580
|
+
else reject(new Error("CODEX_INPUT_CANCELLED"));
|
|
581
|
+
};
|
|
582
|
+
this.pending = {
|
|
583
|
+
id,
|
|
584
|
+
questions: params.questions,
|
|
585
|
+
answers: /* @__PURE__ */ Object.create(null),
|
|
586
|
+
finish,
|
|
587
|
+
approval
|
|
588
|
+
};
|
|
589
|
+
const input = {
|
|
590
|
+
requestId: id,
|
|
591
|
+
questions: structuredClone(params.questions),
|
|
592
|
+
...approval ? { approval: structuredClone(approval) } : {}
|
|
593
|
+
};
|
|
594
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
595
|
+
if (signal.aborted) {
|
|
596
|
+
abort();
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
this.emit({
|
|
600
|
+
type: "message_start",
|
|
601
|
+
message: {
|
|
602
|
+
role: "assistant",
|
|
603
|
+
content: [
|
|
604
|
+
{
|
|
605
|
+
type: "tool_use",
|
|
606
|
+
name: "AskUserQuestion",
|
|
607
|
+
id: eventId,
|
|
608
|
+
input
|
|
609
|
+
}
|
|
610
|
+
]
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
if (this.pending?.id !== id || signal.aborted) return;
|
|
614
|
+
this.emit({
|
|
615
|
+
type: "tool_call",
|
|
616
|
+
name: "AskUserQuestion",
|
|
617
|
+
tool: { name: "AskUserQuestion" },
|
|
618
|
+
toolCallId: eventId,
|
|
619
|
+
input
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
get active() {
|
|
624
|
+
return Boolean(this.pending);
|
|
625
|
+
}
|
|
626
|
+
answer(answer, question, requestId) {
|
|
627
|
+
const pending = this.pending;
|
|
628
|
+
if (!pending || pending.id !== requestId || typeof answer !== "string") return false;
|
|
629
|
+
if (pending.approval && (question !== pending.questions[0]?.question || !["Approve once", "Deny"].includes(answer)))
|
|
630
|
+
return false;
|
|
631
|
+
const q = question === void 0 ? pending.questions.find((entry) => !Object.hasOwn(pending.answers, entry.id)) : pending.questions.find((entry) => entry.question === question);
|
|
632
|
+
if (!q || Object.hasOwn(pending.answers, q.id)) return false;
|
|
633
|
+
pending.answers[q.id] = { answers: [answer] };
|
|
634
|
+
if (pending.questions.every((entry) => Object.hasOwn(pending.answers, entry.id)))
|
|
635
|
+
pending.finish({ answers: pending.answers });
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
// bridge/src/codex/managed-harness.ts
|
|
641
|
+
var record3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
642
|
+
var failure = (code = "CODEX_RUNTIME_UNSUPPORTED") => new Error(code);
|
|
643
|
+
var nativeUsage = (value) => ({
|
|
644
|
+
...typeof value.inputTokens === "number" ? { inputTokens: value.inputTokens } : {},
|
|
645
|
+
...typeof value.outputTokens === "number" ? { outputTokens: value.outputTokens } : {},
|
|
646
|
+
...typeof value.cachedInputTokens === "number" ? { cacheReadTokens: value.cachedInputTokens } : {},
|
|
647
|
+
...typeof value.reasoningOutputTokens === "number" ? { reasoningTokens: value.reasoningOutputTokens } : {},
|
|
648
|
+
...typeof value.totalTokens === "number" ? { totalTokens: value.totalTokens } : {}
|
|
649
|
+
});
|
|
650
|
+
var subtractUsage = (total, baseline) => {
|
|
651
|
+
const delta = (value, before) => value === void 0 ? void 0 : Math.max(0, value - (before ?? 0));
|
|
652
|
+
return {
|
|
653
|
+
...delta(total.inputTokens, baseline?.inputTokens) !== void 0 ? { inputTokens: delta(total.inputTokens, baseline?.inputTokens) } : {},
|
|
654
|
+
...delta(total.outputTokens, baseline?.outputTokens) !== void 0 ? { outputTokens: delta(total.outputTokens, baseline?.outputTokens) } : {},
|
|
655
|
+
...delta(total.cacheReadTokens, baseline?.cacheReadTokens) !== void 0 ? {
|
|
656
|
+
cacheReadTokens: delta(total.cacheReadTokens, baseline?.cacheReadTokens)
|
|
657
|
+
} : {},
|
|
658
|
+
...delta(total.reasoningTokens, baseline?.reasoningTokens) !== void 0 ? {
|
|
659
|
+
reasoningTokens: delta(total.reasoningTokens, baseline?.reasoningTokens)
|
|
660
|
+
} : {},
|
|
661
|
+
...delta(total.totalTokens, baseline?.totalTokens) !== void 0 ? { totalTokens: delta(total.totalTokens, baseline?.totalTokens) } : {}
|
|
662
|
+
};
|
|
663
|
+
};
|
|
664
|
+
var addUsage = (left, right) => {
|
|
665
|
+
const result = {};
|
|
666
|
+
for (const key of [
|
|
667
|
+
"inputTokens",
|
|
668
|
+
"outputTokens",
|
|
669
|
+
"cacheReadTokens",
|
|
670
|
+
"reasoningTokens",
|
|
671
|
+
"totalTokens"
|
|
672
|
+
]) {
|
|
673
|
+
if (left?.[key] !== void 0 || right[key] !== void 0)
|
|
674
|
+
result[key] = (left?.[key] ?? 0) + (right[key] ?? 0);
|
|
675
|
+
}
|
|
676
|
+
return result;
|
|
677
|
+
};
|
|
678
|
+
var limit = (value, fallback) => {
|
|
679
|
+
const result = value ?? fallback;
|
|
680
|
+
if (!Number.isSafeInteger(result) || result <= 0 || result > 18e5)
|
|
681
|
+
throw failure("AI_PROFILE_INVALID");
|
|
682
|
+
return result;
|
|
683
|
+
};
|
|
684
|
+
async function bounded(work, signal, ms) {
|
|
685
|
+
let timer;
|
|
686
|
+
let cancel = () => {
|
|
687
|
+
};
|
|
688
|
+
try {
|
|
689
|
+
return await Promise.race([
|
|
690
|
+
work,
|
|
691
|
+
new Promise((_, reject) => {
|
|
692
|
+
cancel = () => reject(failure());
|
|
693
|
+
if (signal.aborted) cancel();
|
|
694
|
+
else signal.addEventListener("abort", cancel, { once: true });
|
|
695
|
+
timer = setTimeout(() => reject(failure()), ms);
|
|
696
|
+
})
|
|
697
|
+
]);
|
|
698
|
+
} finally {
|
|
699
|
+
clearTimeout(timer);
|
|
700
|
+
signal.removeEventListener("abort", cancel);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
var ManagedCodexHarness = class extends AgentHarness {
|
|
704
|
+
constructor(config) {
|
|
705
|
+
super();
|
|
706
|
+
this.config = config;
|
|
707
|
+
this.desired = {
|
|
708
|
+
model: config.model,
|
|
709
|
+
modelRef: config.modelRef,
|
|
710
|
+
thinking: config.thinking,
|
|
711
|
+
effort: config.effort,
|
|
712
|
+
registry: config.registry,
|
|
713
|
+
roles: config.roles
|
|
714
|
+
};
|
|
715
|
+
this.reconfig = new ReconfigureLedger({
|
|
716
|
+
config: () => this.desired,
|
|
717
|
+
onDesired: ({ model, thinking, effort }) => {
|
|
718
|
+
const fields = {};
|
|
719
|
+
if (model !== void 0) fields.model = model;
|
|
720
|
+
if (thinking !== void 0) fields.thinking = thinking;
|
|
721
|
+
if (effort !== void 0) fields.effort = effort;
|
|
722
|
+
this.pendingPatch = { ...this.pendingPatch, ...fields };
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
config;
|
|
727
|
+
harnessInfo = {
|
|
728
|
+
id: "codex",
|
|
729
|
+
name: "OpenAI Codex",
|
|
730
|
+
modelAgnostic: false,
|
|
731
|
+
supportsInBandAbort: true,
|
|
732
|
+
capabilities: BUILTIN_HARNESS_CATALOG.codex?.capabilities
|
|
733
|
+
};
|
|
734
|
+
client;
|
|
735
|
+
connecting;
|
|
736
|
+
options;
|
|
737
|
+
credential;
|
|
738
|
+
nativeApprovalPolicy;
|
|
739
|
+
instance;
|
|
740
|
+
tools;
|
|
741
|
+
mcpStartup = "unknown";
|
|
742
|
+
pendingMcpStartup;
|
|
743
|
+
mcpDiagnostics = {
|
|
744
|
+
startupBeforeTurn: "unknown",
|
|
745
|
+
startupAtEnd: "unknown",
|
|
746
|
+
toolsListBeforeTurn: 0,
|
|
747
|
+
toolsListTotal: 0,
|
|
748
|
+
toolsCallTotal: 0,
|
|
749
|
+
nativeMcpToolItemCount: 0
|
|
750
|
+
};
|
|
751
|
+
controller = new AbortController();
|
|
752
|
+
generation = 0;
|
|
753
|
+
killed = false;
|
|
754
|
+
broken = false;
|
|
755
|
+
cleanupUncertain = false;
|
|
756
|
+
unexpectedClose = false;
|
|
757
|
+
starting;
|
|
758
|
+
closing = Promise.resolve();
|
|
759
|
+
threadId;
|
|
760
|
+
lastThreadId;
|
|
761
|
+
resetting;
|
|
762
|
+
pendingPatch;
|
|
763
|
+
active;
|
|
764
|
+
threadUsageTotal;
|
|
765
|
+
resumeUsageTurnIds = /* @__PURE__ */ new Set();
|
|
766
|
+
queue = Promise.resolve();
|
|
767
|
+
queued = 0;
|
|
768
|
+
admittedTurns = 0;
|
|
769
|
+
loginCompleted;
|
|
770
|
+
/** Recovery budget for the connection's startup (no active turn); reset on each start. */
|
|
771
|
+
startupRecovery = freshBudget();
|
|
772
|
+
startupAuthAttempted = false;
|
|
773
|
+
questions = new CodexQuestions((event) => this.event(event));
|
|
774
|
+
get acceptsReplyAsPrompt() {
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
hasPendingQuestion() {
|
|
778
|
+
return this.questions.active;
|
|
779
|
+
}
|
|
780
|
+
answerQuestion(answer, question, requestId) {
|
|
781
|
+
return this.questions.answer(answer, question, requestId);
|
|
782
|
+
}
|
|
783
|
+
/** The settings a later prompt will run on; `config` only takes them at the next turn. */
|
|
784
|
+
desired;
|
|
785
|
+
reconfig;
|
|
786
|
+
get runtimeSessionId() {
|
|
787
|
+
return this.threadId ?? this.lastThreadId;
|
|
788
|
+
}
|
|
789
|
+
get isRunning() {
|
|
790
|
+
return Boolean(this.client && this.threadId && !this.killed && !this.broken);
|
|
791
|
+
}
|
|
792
|
+
getModel() {
|
|
793
|
+
return this.config.model;
|
|
794
|
+
}
|
|
795
|
+
async start() {
|
|
796
|
+
if (this.killed || this.broken) throw failure();
|
|
797
|
+
if (this.threadId && this.client) return;
|
|
798
|
+
if (this.starting) return this.starting;
|
|
799
|
+
this.starting = this.initialize().catch(async (error) => {
|
|
800
|
+
this.broken = true;
|
|
801
|
+
await this.closeGeneration();
|
|
802
|
+
if (error instanceof CodexNativeStateUnavailable && (this.cleanupUncertain || this.unexpectedClose || this.killed || this.startupAuthAttempted))
|
|
803
|
+
throw failure();
|
|
804
|
+
const safe = error instanceof Error && /^(AI_PROFILE_INVALID|CODEX_[A-Z_]+)$/.test(error.message) ? error : failure();
|
|
805
|
+
throw safe;
|
|
806
|
+
}).finally(() => {
|
|
807
|
+
this.starting = void 0;
|
|
808
|
+
});
|
|
809
|
+
return this.starting;
|
|
810
|
+
}
|
|
811
|
+
async initialize() {
|
|
812
|
+
await this.closing;
|
|
813
|
+
if (this.killed) throw failure();
|
|
814
|
+
const options = this.config.harnessOptions?.codex?.managedRuntime;
|
|
815
|
+
const tuning = this.config.harnessOptions?.codex;
|
|
816
|
+
if (tuning?.authSource !== "managed" || !options || typeof options.prepareTools !== "function" || !isAbsolute(options.executable) || !this.config.model || !tuning.reasoningEffort || !tuning.approvalPolicy || tuning.approvalPolicy === "on-failure" || !tuning.sandboxMode || tuning.sandboxMode === "danger-full-access" || // A managed session runs on the platform-minted credential alone: an
|
|
817
|
+
// OpenAI key handed in through the child environment would be a second,
|
|
818
|
+
// unaudited authority.
|
|
819
|
+
this.config.env?.OPENAI_API_KEY || // Credential isolation, stated on the AI provider registry: one entry,
|
|
820
|
+
// codex's own OpenAI surface, a platform-injected oauth reference. See
|
|
821
|
+
// `managedCodexRegistryViolation`.
|
|
822
|
+
managedCodexRegistryViolation(this.config.registry) !== void 0 || options.nativeTurnBudget !== void 0 && options.nativeTurnBudget !== 1)
|
|
823
|
+
throw failure("AI_PROFILE_INVALID");
|
|
824
|
+
this.options = options;
|
|
825
|
+
const timeout = limit(options.requestTimeoutMs, 15e3);
|
|
826
|
+
limit(options.turnTimeoutMs, 3e5);
|
|
827
|
+
try {
|
|
828
|
+
this.credential = parseCodexCredential(this.credential ?? options.credential);
|
|
829
|
+
} catch {
|
|
830
|
+
throw failure("CODEX_AUTH_UNAVAILABLE");
|
|
831
|
+
}
|
|
832
|
+
if (this.credential.configId !== this.config.aiProviderConfigId)
|
|
833
|
+
throw failure("AI_PROFILE_INVALID");
|
|
834
|
+
const privatePolicy = prepareNativePolicy({
|
|
835
|
+
cwd: this.config.cwd,
|
|
836
|
+
privateRoot: options.privateRoot,
|
|
837
|
+
privateDirectory: options.privateDirectory,
|
|
838
|
+
sandboxMode: tuning.sandboxMode
|
|
839
|
+
});
|
|
840
|
+
this.instance = prepareCodexInstance(this.config, options, this.credential);
|
|
841
|
+
const generation = ++this.generation;
|
|
842
|
+
const mcpReplies = /* @__PURE__ */ new WeakMap();
|
|
843
|
+
this.controller = new AbortController();
|
|
844
|
+
this.startupRecovery = freshBudget();
|
|
845
|
+
this.startupAuthAttempted = false;
|
|
846
|
+
const handlers = {
|
|
847
|
+
"account/chatgptAuthTokens/refresh": async (params, signal) => {
|
|
848
|
+
const active2 = this.active;
|
|
849
|
+
if (!active2 && !this.starting) throw failure("CODEX_AUTH_UNAVAILABLE");
|
|
850
|
+
if (!active2) this.startupAuthAttempted = true;
|
|
851
|
+
const cancellation = AbortSignal.any([
|
|
852
|
+
signal,
|
|
853
|
+
this.controller.signal,
|
|
854
|
+
...active2 ? [active2.controller.signal] : []
|
|
855
|
+
]);
|
|
856
|
+
try {
|
|
857
|
+
return await this.refresh(params, cancellation);
|
|
858
|
+
} catch {
|
|
859
|
+
if (active2 && this.active === active2) active2.failureCode = "CODEX_AUTH_UNAVAILABLE";
|
|
860
|
+
throw failure("CODEX_AUTH_UNAVAILABLE");
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
const authMode = this.credential.kind === "codex-subscription" ? "chatgpt" : "api";
|
|
865
|
+
const nativePolicy = {
|
|
866
|
+
cli_auth_credentials_store: "ephemeral",
|
|
867
|
+
forced_login_method: authMode,
|
|
868
|
+
model_provider: "openai",
|
|
869
|
+
model: this.config.model,
|
|
870
|
+
model_instructions_file: this.instance.instructionFile,
|
|
871
|
+
shell_environment_policy: {
|
|
872
|
+
inherit: "none",
|
|
873
|
+
set: { PATH: options.environment.PATH ?? "/usr/bin:/bin" }
|
|
874
|
+
},
|
|
875
|
+
mcp_servers: {}
|
|
876
|
+
};
|
|
877
|
+
const overrides = Object.entries({
|
|
878
|
+
cli_auth_credentials_store: "ephemeral",
|
|
879
|
+
forced_login_method: authMode,
|
|
880
|
+
model_provider: "openai",
|
|
881
|
+
model: this.config.model,
|
|
882
|
+
model_instructions_file: this.instance.instructionFile,
|
|
883
|
+
"shell_environment_policy.inherit": "none",
|
|
884
|
+
"shell_environment_policy.set.PATH": options.environment.PATH ?? "/usr/bin:/bin"
|
|
885
|
+
}).flatMap(([key, value]) => ["-c", `${key}=${JSON.stringify(value)}`]);
|
|
886
|
+
overrides.push("-c", "mcp_servers={}");
|
|
887
|
+
const connecting = CodexAppServerClient.connect(
|
|
888
|
+
{
|
|
889
|
+
command: options.executable,
|
|
890
|
+
args: [
|
|
891
|
+
...options.executableArgs ?? [],
|
|
892
|
+
...overrides,
|
|
893
|
+
...privatePolicy.cliArgs,
|
|
894
|
+
"app-server",
|
|
895
|
+
"--stdio"
|
|
896
|
+
],
|
|
897
|
+
cwd: this.instance.root,
|
|
898
|
+
env: codexInstanceEnvironment(options.environment, this.instance.home),
|
|
899
|
+
clientInfo: { name: "skaile-managed-codex", version: "1" },
|
|
900
|
+
processGroup: true,
|
|
901
|
+
requestTimeoutMs: timeout,
|
|
902
|
+
serverRequestTimeoutMs: Math.min(limit(options.interactionTimeoutMs, 3e5), 3e5),
|
|
903
|
+
onServerRequest: handlers,
|
|
904
|
+
canReplyToServerRequest: (method, params) => {
|
|
905
|
+
if (method !== "mcpServer/elicitation/request") return true;
|
|
906
|
+
return record3(params) && (mcpReplies.get(params)?.() ?? false);
|
|
907
|
+
},
|
|
908
|
+
onNotification: (method, params) => {
|
|
909
|
+
if (generation === this.generation) this.notification(method, params);
|
|
910
|
+
},
|
|
911
|
+
onClose: () => {
|
|
912
|
+
if (generation !== this.generation) return;
|
|
913
|
+
this.unexpectedClose = true;
|
|
914
|
+
this.broken = true;
|
|
915
|
+
this.loginCompleted?.reject(failure());
|
|
916
|
+
this.finishTurn("CODEX_RUNTIME_UNSUPPORTED");
|
|
917
|
+
void this.closeGeneration();
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
this.controller.signal
|
|
921
|
+
);
|
|
922
|
+
this.connecting = connecting;
|
|
923
|
+
try {
|
|
924
|
+
this.client = await connecting;
|
|
925
|
+
} finally {
|
|
926
|
+
if (this.connecting === connecting) this.connecting = void 0;
|
|
927
|
+
}
|
|
928
|
+
if (generation !== this.generation || this.killed) throw failure();
|
|
929
|
+
await this.authenticate(timeout);
|
|
930
|
+
const toolsPromise = options.prepareTools(this.controller.signal).then(async (tools) => {
|
|
931
|
+
if (generation !== this.generation || this.controller.signal.aborted) {
|
|
932
|
+
await tools.dispose().catch(() => {
|
|
933
|
+
});
|
|
934
|
+
throw failure();
|
|
935
|
+
}
|
|
936
|
+
return tools;
|
|
937
|
+
});
|
|
938
|
+
try {
|
|
939
|
+
this.tools = await bounded(toolsPromise, this.controller.signal, timeout);
|
|
940
|
+
} catch {
|
|
941
|
+
throw failure("CODEX_REQUIRED_TOOL_UNAVAILABLE");
|
|
942
|
+
}
|
|
943
|
+
if (!this.tools || !record3(this.tools.mcpServers) || typeof this.tools.dispose !== "function" || Object.keys(this.tools.mcpServers).length === 0 && Object.keys(this.tools.onServerRequest ?? {}).length === 0)
|
|
944
|
+
throw failure("CODEX_REQUIRED_TOOL_UNAVAILABLE");
|
|
945
|
+
if (this.tools.onServerRequest?.["item/tool/requestUserInput"])
|
|
946
|
+
throw failure("AI_PROFILE_INVALID");
|
|
947
|
+
const nativeApprovals = createCodexNativeApprovalHandlers({
|
|
948
|
+
questions: this.questions,
|
|
949
|
+
getPolicy: () => tuning.approvalPolicy === "never" ? void 0 : this.nativeApprovalPolicy,
|
|
950
|
+
getFileChanges: ({ itemId, threadId, turnId }) => {
|
|
951
|
+
const active2 = this.active;
|
|
952
|
+
if (!active2 || active2.controller.signal.aborted || threadId !== this.threadId || turnId !== active2.id)
|
|
953
|
+
return void 0;
|
|
954
|
+
const snapshot = active2.fileChanges.get(itemId);
|
|
955
|
+
return snapshot ? JSON.parse(snapshot) : void 0;
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
const approvalTools = this.tools;
|
|
959
|
+
const approvalServerName = approvalTools.approvalServerName;
|
|
960
|
+
const approvalServer = approvalServerName ? approvalTools.mcpServers[approvalServerName] : void 0;
|
|
961
|
+
const approvalServerSnapshot = structuredClone(approvalServer);
|
|
962
|
+
const mcpBindingActive = () => this.tools === approvalTools && approvalTools.approvalServerName === approvalServerName && Boolean(approvalServerName) && Object.hasOwn(approvalTools.mcpServers, approvalServerName) && record3(approvalServer) && approvalTools.mcpServers[approvalServerName] === approvalServer && isDeepStrictEqual(approvalServer, approvalServerSnapshot);
|
|
963
|
+
const mcpPolicy = () => tuning.approvalPolicy === "never" ? void 0 : this.nativeApprovalPolicy;
|
|
964
|
+
const mcpApproval = createCodexMcpApprovalHandler({
|
|
965
|
+
serverName: approvalServerName,
|
|
966
|
+
questions: this.questions,
|
|
967
|
+
getPolicy: mcpPolicy,
|
|
968
|
+
bindingActive: mcpBindingActive
|
|
969
|
+
});
|
|
970
|
+
for (const [method, handler] of Object.entries({
|
|
971
|
+
...this.tools.onServerRequest,
|
|
972
|
+
...nativeApprovals,
|
|
973
|
+
"item/tool/requestUserInput": this.questions.request,
|
|
974
|
+
"mcpServer/elicitation/request": mcpApproval
|
|
975
|
+
})) {
|
|
976
|
+
if (method === "account/chatgptAuthTokens/refresh" || !handler)
|
|
977
|
+
throw failure("AI_PROFILE_INVALID");
|
|
978
|
+
handlers[method] = async (params, signal) => {
|
|
979
|
+
const active2 = this.active;
|
|
980
|
+
if (generation !== this.generation || !active2 || !record3(params)) throw failure();
|
|
981
|
+
await active2.ready;
|
|
982
|
+
if (generation !== this.generation || this.active !== active2 || params.threadId !== this.threadId || params.turnId !== active2.id)
|
|
983
|
+
throw failure();
|
|
984
|
+
const cancellation = AbortSignal.any([signal, active2.controller.signal]);
|
|
985
|
+
if (cancellation.aborted) throw failure();
|
|
986
|
+
if (method === "mcpServer/elicitation/request") {
|
|
987
|
+
mcpReplies.set(
|
|
988
|
+
params,
|
|
989
|
+
() => generation === this.generation && this.active === active2 && !cancellation.aborted && params.threadId === this.threadId && params.turnId === active2.id && mcpBindingActive() && isNativeApprovalPolicyActive(mcpPolicy(), this.threadId ?? "")
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
const result = await handler(params, cancellation);
|
|
993
|
+
if (generation !== this.generation || cancellation.aborted || this.active !== active2)
|
|
994
|
+
throw failure();
|
|
995
|
+
return result;
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
if (this.tools.instructions) this.instance.setToolInstructions(this.tools.instructions);
|
|
999
|
+
const threadParams = {
|
|
1000
|
+
model: this.config.model,
|
|
1001
|
+
modelProvider: "openai",
|
|
1002
|
+
cwd: this.instance.cwd,
|
|
1003
|
+
approvalPolicy: tuning.approvalPolicy,
|
|
1004
|
+
permissions: "managed",
|
|
1005
|
+
config: {
|
|
1006
|
+
...nativePolicy,
|
|
1007
|
+
...privatePolicy.config,
|
|
1008
|
+
mcp_servers: this.tools.mcpServers,
|
|
1009
|
+
model_reasoning_effort: this.config.effort ?? tuning.reasoningEffort
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
const resuming = Boolean(this.config.resumeSessionId);
|
|
1013
|
+
this.threadUsageTotal = resuming ? this.threadUsageTotal ?? {} : {};
|
|
1014
|
+
this.resumeUsageTurnIds.clear();
|
|
1015
|
+
const response = resuming ? await this.client.request("thread/resume", {
|
|
1016
|
+
...threadParams,
|
|
1017
|
+
threadId: this.config.resumeSessionId,
|
|
1018
|
+
excludeTurns: false
|
|
1019
|
+
}) : await this.client.request("thread/start", {
|
|
1020
|
+
...threadParams,
|
|
1021
|
+
allowProviderModelFallback: false
|
|
1022
|
+
});
|
|
1023
|
+
if (!record3(response) || response.model !== this.config.model)
|
|
1024
|
+
throw failure("CODEX_MODEL_UNAVAILABLE");
|
|
1025
|
+
if (!record3(response.thread) || typeof response.thread.id !== "string" || resuming && response.thread.id !== this.config.resumeSessionId || resuming && !Array.isArray(response.thread.turns) || response.reasoningEffort !== (this.config.effort ?? tuning.reasoningEffort) || response.modelProvider !== "openai" || response.approvalPolicy !== tuning.approvalPolicy)
|
|
1026
|
+
throw failure("AI_PROFILE_INVALID");
|
|
1027
|
+
if (generation !== this.generation || this.killed) throw failure();
|
|
1028
|
+
this.resumeUsageTurnIds = new Set(
|
|
1029
|
+
resuming && Array.isArray(response.thread.turns) ? response.thread.turns.flatMap(
|
|
1030
|
+
(turn) => record3(turn) && typeof turn.id === "string" ? [turn.id] : []
|
|
1031
|
+
) : []
|
|
1032
|
+
);
|
|
1033
|
+
this.nativeApprovalPolicy = activateNativeApprovalPolicy(
|
|
1034
|
+
privatePolicy,
|
|
1035
|
+
response,
|
|
1036
|
+
this.controller.signal
|
|
1037
|
+
);
|
|
1038
|
+
this.threadId = response.thread.id;
|
|
1039
|
+
this.mcpStartup = this.pendingMcpStartup?.threadId === this.threadId ? this.pendingMcpStartup.status : "unknown";
|
|
1040
|
+
this.pendingMcpStartup = void 0;
|
|
1041
|
+
this.lastThreadId = this.threadId;
|
|
1042
|
+
this.instance.bindThread(this.threadId);
|
|
1043
|
+
}
|
|
1044
|
+
async authenticate(timeout) {
|
|
1045
|
+
const credential = this.credential;
|
|
1046
|
+
if (credential.kind === "openai-api-key") {
|
|
1047
|
+
const result = await this.client.request("account/login/start", {
|
|
1048
|
+
type: "apiKey",
|
|
1049
|
+
apiKey: credential.apiKey
|
|
1050
|
+
});
|
|
1051
|
+
if (result.type !== "apiKey") throw failure("CODEX_AUTH_UNAVAILABLE");
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
const completion = new Promise((resolve3, reject) => {
|
|
1055
|
+
this.loginCompleted = { resolve: resolve3, reject };
|
|
1056
|
+
});
|
|
1057
|
+
const wait = bounded(completion, this.controller.signal, timeout);
|
|
1058
|
+
void wait.catch(() => {
|
|
1059
|
+
});
|
|
1060
|
+
try {
|
|
1061
|
+
const result = await this.client.request("account/login/start", {
|
|
1062
|
+
type: "chatgptAuthTokens",
|
|
1063
|
+
accessToken: credential.accessToken,
|
|
1064
|
+
chatgptAccountId: credential.chatgptAccountId,
|
|
1065
|
+
chatgptPlanType: credential.chatgptPlanType ?? null
|
|
1066
|
+
});
|
|
1067
|
+
if (result.type !== "chatgptAuthTokens") throw failure("CODEX_AUTH_UNAVAILABLE");
|
|
1068
|
+
await wait;
|
|
1069
|
+
} finally {
|
|
1070
|
+
this.loginCompleted = void 0;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
async refresh(params, signal) {
|
|
1074
|
+
const credential = this.credential;
|
|
1075
|
+
if (!record3(params) || params.reason !== "unauthorized" || !credential || credential.kind !== "codex-subscription" || params.previousAccountId != null && params.previousAccountId !== credential.chatgptAccountId || !this.options?.refreshCredential)
|
|
1076
|
+
throw failure("CODEX_AUTH_UNAVAILABLE");
|
|
1077
|
+
const active2 = this.active;
|
|
1078
|
+
const budget = active2 ? active2.recovery : this.startupRecovery;
|
|
1079
|
+
const decision = decide(
|
|
1080
|
+
{ message: "unauthorized", authError: true, limitRejected: false, queryEnded: false },
|
|
1081
|
+
budget,
|
|
1082
|
+
{ onAuthError: true, onLimitBlocked: false, resumableSessionId: false }
|
|
1083
|
+
);
|
|
1084
|
+
if (!("attempt" in decision) || decision.attempt !== "auth_refresh")
|
|
1085
|
+
throw failure("CODEX_AUTH_UNAVAILABLE");
|
|
1086
|
+
const spent = spend(budget, "auth_refresh");
|
|
1087
|
+
if (active2) active2.recovery = spent;
|
|
1088
|
+
else this.startupRecovery = spent;
|
|
1089
|
+
const result = await this.options.refreshCredential(
|
|
1090
|
+
{
|
|
1091
|
+
kind: "codex-subscription",
|
|
1092
|
+
configId: credential.configId,
|
|
1093
|
+
credentialVersion: credential.credentialVersion,
|
|
1094
|
+
reason: "retry-401"
|
|
1095
|
+
},
|
|
1096
|
+
signal
|
|
1097
|
+
);
|
|
1098
|
+
if (signal.aborted) throw failure("CODEX_AUTH_UNAVAILABLE");
|
|
1099
|
+
let parsed;
|
|
1100
|
+
try {
|
|
1101
|
+
parsed = parseCodexRefreshResult(result, credential);
|
|
1102
|
+
} catch {
|
|
1103
|
+
throw failure("CODEX_AUTH_UNAVAILABLE");
|
|
1104
|
+
}
|
|
1105
|
+
if (!parsed.ok) throw failure("CODEX_AUTH_UNAVAILABLE");
|
|
1106
|
+
this.credential = parsed.credential;
|
|
1107
|
+
return {
|
|
1108
|
+
accessToken: parsed.credential.accessToken,
|
|
1109
|
+
chatgptAccountId: parsed.credential.chatgptAccountId,
|
|
1110
|
+
chatgptPlanType: parsed.credential.chatgptPlanType ?? null
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
prompt(message) {
|
|
1114
|
+
return this.promptInput({ text: message });
|
|
1115
|
+
}
|
|
1116
|
+
promptInput(input) {
|
|
1117
|
+
if (input.toolAllowlist !== void 0)
|
|
1118
|
+
return Promise.reject(new Error(TOOL_ALLOWLIST_UNSUPPORTED));
|
|
1119
|
+
const message = structuredClone(input);
|
|
1120
|
+
const turnBudget = this.config.harnessOptions?.codex?.managedRuntime?.nativeTurnBudget;
|
|
1121
|
+
if (this.killed || this.broken || this.resetting || this.queued >= 32 || turnBudget !== void 0 && this.admittedTurns >= turnBudget)
|
|
1122
|
+
return Promise.reject(failure());
|
|
1123
|
+
if (turnBudget !== void 0) this.admittedTurns++;
|
|
1124
|
+
this.queued++;
|
|
1125
|
+
const task = this.queue.then(() => this.runTurn(message));
|
|
1126
|
+
this.queue = task.catch(() => {
|
|
1127
|
+
}).finally(() => {
|
|
1128
|
+
this.queued--;
|
|
1129
|
+
});
|
|
1130
|
+
return task;
|
|
1131
|
+
}
|
|
1132
|
+
async runTurn(message) {
|
|
1133
|
+
if (this.resetting) throw failure();
|
|
1134
|
+
await this.start();
|
|
1135
|
+
if (this.killed || this.broken || this.resetting) throw failure();
|
|
1136
|
+
if (this.pendingPatch) {
|
|
1137
|
+
this.config = { ...this.config, ...this.pendingPatch };
|
|
1138
|
+
this.pendingPatch = void 0;
|
|
1139
|
+
}
|
|
1140
|
+
if (!this.config.model) throw failure("AI_PROFILE_INVALID");
|
|
1141
|
+
await this.tools?.validate?.();
|
|
1142
|
+
if (this.tools?.needsReload?.()) {
|
|
1143
|
+
this.config = { ...this.config, resumeSessionId: this.threadId };
|
|
1144
|
+
await this.closeGeneration();
|
|
1145
|
+
await this.start();
|
|
1146
|
+
}
|
|
1147
|
+
const input = codexPromptInput(
|
|
1148
|
+
message,
|
|
1149
|
+
this.instance.cwd,
|
|
1150
|
+
this.options?.skillsDirectory ? {
|
|
1151
|
+
source: this.options.skillsDirectory,
|
|
1152
|
+
target: join(this.instance.home, "skills")
|
|
1153
|
+
} : void 0
|
|
1154
|
+
);
|
|
1155
|
+
try {
|
|
1156
|
+
parseCodexCredential(this.credential);
|
|
1157
|
+
} catch {
|
|
1158
|
+
throw failure("CODEX_AUTH_UNAVAILABLE");
|
|
1159
|
+
}
|
|
1160
|
+
let finish;
|
|
1161
|
+
let readyNow;
|
|
1162
|
+
const ready = new Promise((resolve3) => {
|
|
1163
|
+
readyNow = resolve3;
|
|
1164
|
+
});
|
|
1165
|
+
const done = new Promise((resolve3) => {
|
|
1166
|
+
finish = resolve3;
|
|
1167
|
+
});
|
|
1168
|
+
const active2 = {
|
|
1169
|
+
controller: new AbortController(),
|
|
1170
|
+
done,
|
|
1171
|
+
finish,
|
|
1172
|
+
ready,
|
|
1173
|
+
readyNow,
|
|
1174
|
+
timer: setTimeout(
|
|
1175
|
+
() => {
|
|
1176
|
+
this.finishTurn("CODEX_RUNTIME_UNSUPPORTED");
|
|
1177
|
+
this.broken = true;
|
|
1178
|
+
void this.closeGeneration();
|
|
1179
|
+
},
|
|
1180
|
+
limit(this.options?.turnTimeoutMs, 3e5)
|
|
1181
|
+
),
|
|
1182
|
+
events: [],
|
|
1183
|
+
messages: /* @__PURE__ */ new Map(),
|
|
1184
|
+
fileChanges: /* @__PURE__ */ new Map(),
|
|
1185
|
+
toolResults: [],
|
|
1186
|
+
toolItems: /* @__PURE__ */ new Set(),
|
|
1187
|
+
completedToolItems: /* @__PURE__ */ new Set(),
|
|
1188
|
+
recovery: freshBudget(),
|
|
1189
|
+
usageBaseline: this.threadUsageTotal,
|
|
1190
|
+
actualModel: this.config.model
|
|
1191
|
+
};
|
|
1192
|
+
this.active = active2;
|
|
1193
|
+
this.mcpDiagnostics.nativeMcpToolItemCount = 0;
|
|
1194
|
+
this.observeMcp(true);
|
|
1195
|
+
this.tools?.beginTurn?.(active2.controller.signal);
|
|
1196
|
+
try {
|
|
1197
|
+
const response = await this.client.request("turn/start", {
|
|
1198
|
+
threadId: this.threadId,
|
|
1199
|
+
permissions: "managed",
|
|
1200
|
+
input,
|
|
1201
|
+
model: this.config.model,
|
|
1202
|
+
effort: this.config.effort ?? this.config.harnessOptions.codex.reasoningEffort
|
|
1203
|
+
});
|
|
1204
|
+
if (this.active !== active2) return;
|
|
1205
|
+
if (!record3(response.turn) || typeof response.turn.id !== "string") throw failure();
|
|
1206
|
+
active2.id = response.turn.id;
|
|
1207
|
+
active2.readyNow();
|
|
1208
|
+
for (const event of active2.events.splice(0)) this.notification(event.method, event.params);
|
|
1209
|
+
if (this.active === active2 && response.turn.status !== "inProgress")
|
|
1210
|
+
this.complete(response.turn);
|
|
1211
|
+
await done;
|
|
1212
|
+
} catch {
|
|
1213
|
+
this.finishTurn("CODEX_RUNTIME_UNSUPPORTED");
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
async abort() {
|
|
1217
|
+
const active2 = this.active;
|
|
1218
|
+
if (!active2) return;
|
|
1219
|
+
active2.controller.abort();
|
|
1220
|
+
try {
|
|
1221
|
+
if (!active2.id)
|
|
1222
|
+
await bounded(
|
|
1223
|
+
active2.ready,
|
|
1224
|
+
this.controller.signal,
|
|
1225
|
+
limit(this.options?.requestTimeoutMs, 15e3)
|
|
1226
|
+
);
|
|
1227
|
+
if (this.active !== active2) return;
|
|
1228
|
+
await this.client.request("turn/interrupt", {
|
|
1229
|
+
threadId: this.threadId,
|
|
1230
|
+
turnId: active2.id
|
|
1231
|
+
});
|
|
1232
|
+
await bounded(
|
|
1233
|
+
active2.done,
|
|
1234
|
+
this.controller.signal,
|
|
1235
|
+
limit(this.options?.requestTimeoutMs, 15e3)
|
|
1236
|
+
);
|
|
1237
|
+
} catch {
|
|
1238
|
+
this.finishTurn("CODEX_RUNTIME_UNSUPPORTED");
|
|
1239
|
+
this.broken = true;
|
|
1240
|
+
await this.closeGeneration();
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
kill() {
|
|
1244
|
+
if (this.killed) return;
|
|
1245
|
+
this.killed = true;
|
|
1246
|
+
this.finishTurn("CODEX_RUNTIME_UNSUPPORTED");
|
|
1247
|
+
void this.closeGeneration();
|
|
1248
|
+
}
|
|
1249
|
+
async dispose() {
|
|
1250
|
+
this.kill();
|
|
1251
|
+
await this.starting?.catch(() => {
|
|
1252
|
+
});
|
|
1253
|
+
await this.closing;
|
|
1254
|
+
}
|
|
1255
|
+
async resetSession() {
|
|
1256
|
+
if (this.killed || this.broken) throw failure();
|
|
1257
|
+
if (this.resetting) return this.resetting;
|
|
1258
|
+
this.resetting = Promise.resolve().then(async () => {
|
|
1259
|
+
if (this.active) await this.abort();
|
|
1260
|
+
await this.queue;
|
|
1261
|
+
await this.starting;
|
|
1262
|
+
await this.closeGeneration();
|
|
1263
|
+
if (this.killed || this.broken) throw failure();
|
|
1264
|
+
this.config = { ...this.config, resumeSessionId: void 0 };
|
|
1265
|
+
this.lastThreadId = void 0;
|
|
1266
|
+
this.threadUsageTotal = void 0;
|
|
1267
|
+
}).finally(() => {
|
|
1268
|
+
this.resetting = void 0;
|
|
1269
|
+
});
|
|
1270
|
+
return this.resetting;
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Store a model / thinking / effort change; the next prompt merges it into the
|
|
1274
|
+
* config before the turn starts, and a turn in flight keeps what it began with.
|
|
1275
|
+
*/
|
|
1276
|
+
reconfigure(patch) {
|
|
1277
|
+
return this.reconfig.reconfigure(patch);
|
|
1278
|
+
}
|
|
1279
|
+
closeGeneration() {
|
|
1280
|
+
const generation = ++this.generation;
|
|
1281
|
+
const finalMcp = { ...this.mcpDiagnostics };
|
|
1282
|
+
this.mcpStartup = "unknown";
|
|
1283
|
+
this.pendingMcpStartup = void 0;
|
|
1284
|
+
this.controller.abort();
|
|
1285
|
+
this.nativeApprovalPolicy = void 0;
|
|
1286
|
+
this.loginCompleted?.reject(failure());
|
|
1287
|
+
this.loginCompleted = void 0;
|
|
1288
|
+
const client = this.client;
|
|
1289
|
+
this.client = void 0;
|
|
1290
|
+
const connecting = this.connecting;
|
|
1291
|
+
this.connecting = void 0;
|
|
1292
|
+
const tools = this.tools;
|
|
1293
|
+
this.tools = void 0;
|
|
1294
|
+
const instance = this.instance;
|
|
1295
|
+
this.instance = void 0;
|
|
1296
|
+
this.threadId = void 0;
|
|
1297
|
+
this.closing = this.closing.then(async () => {
|
|
1298
|
+
try {
|
|
1299
|
+
const pendingClient = await connecting?.catch(() => void 0);
|
|
1300
|
+
await client?.dispose();
|
|
1301
|
+
if (pendingClient && pendingClient !== client) await pendingClient.dispose();
|
|
1302
|
+
if (client && !client.terminationConfirmed || pendingClient && !pendingClient.terminationConfirmed)
|
|
1303
|
+
this.cleanupUncertain = true;
|
|
1304
|
+
if (tools) {
|
|
1305
|
+
try {
|
|
1306
|
+
await bounded(
|
|
1307
|
+
Promise.resolve().then(() => tools.dispose()),
|
|
1308
|
+
new AbortController().signal,
|
|
1309
|
+
1e3
|
|
1310
|
+
);
|
|
1311
|
+
if (generation === this.generation && !this.cleanupUncertain)
|
|
1312
|
+
this.observeClosedMcp(tools, finalMcp);
|
|
1313
|
+
} catch {
|
|
1314
|
+
this.cleanupUncertain = true;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
} finally {
|
|
1318
|
+
instance?.release();
|
|
1319
|
+
}
|
|
1320
|
+
}).catch(() => {
|
|
1321
|
+
this.cleanupUncertain = true;
|
|
1322
|
+
});
|
|
1323
|
+
return this.closing;
|
|
1324
|
+
}
|
|
1325
|
+
notification(method, params) {
|
|
1326
|
+
if (!record3(params)) return;
|
|
1327
|
+
if (method === "mcpServer/startupStatus/updated") {
|
|
1328
|
+
const serverName = this.tools?.diagnostics?.serverName;
|
|
1329
|
+
if (!serverName || params.name !== serverName || typeof params.threadId !== "string" || params.threadId.length > 512 || typeof params.status !== "string" || !["starting", "ready", "failed", "cancelled"].includes(params.status))
|
|
1330
|
+
return;
|
|
1331
|
+
const status = params.status;
|
|
1332
|
+
if (this.threadId) {
|
|
1333
|
+
if (params.threadId === this.threadId) this.mcpStartup = status;
|
|
1334
|
+
} else this.pendingMcpStartup = { threadId: params.threadId, status };
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
if (method === "account/login/completed") {
|
|
1338
|
+
if (params.success === true && params.loginId == null) this.loginCompleted?.resolve();
|
|
1339
|
+
else this.loginCompleted?.reject(failure("CODEX_AUTH_UNAVAILABLE"));
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
const active2 = this.active;
|
|
1343
|
+
if ((!active2 || !active2.usageCursor && this.resumeUsageTurnIds.has(String(params.turnId))) && method === "thread/tokenUsage/updated" && params.threadId === (this.threadId ?? this.config.resumeSessionId) && record3(params.tokenUsage) && record3(params.tokenUsage.total)) {
|
|
1344
|
+
this.threadUsageTotal = nativeUsage(params.tokenUsage.total);
|
|
1345
|
+
if (active2) active2.usageBaseline = this.threadUsageTotal;
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
if (!active2 || params.threadId !== this.threadId) return;
|
|
1349
|
+
if (!active2.id) {
|
|
1350
|
+
if (active2.events.length >= 256) {
|
|
1351
|
+
this.broken = true;
|
|
1352
|
+
this.finishTurn("CODEX_RUNTIME_UNSUPPORTED");
|
|
1353
|
+
void this.closeGeneration();
|
|
1354
|
+
} else active2.events.push({ method, params });
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
const turnId = record3(params.turn) ? params.turn.id : params.turnId;
|
|
1358
|
+
if (turnId !== active2.id) return;
|
|
1359
|
+
if (method === "thread/tokenUsage/updated" && record3(params.tokenUsage)) {
|
|
1360
|
+
const last = params.tokenUsage.last;
|
|
1361
|
+
const total = params.tokenUsage.total;
|
|
1362
|
+
const cumulativeUsage = record3(total) ? nativeUsage(total) : void 0;
|
|
1363
|
+
if (cumulativeUsage) {
|
|
1364
|
+
active2.usageBaseline ??= {};
|
|
1365
|
+
this.resumeUsageTurnIds.clear();
|
|
1366
|
+
const cursor = active2.usageCursor ?? active2.usageBaseline;
|
|
1367
|
+
const reset = cursor?.totalTokens !== void 0 && cumulativeUsage.totalTokens !== void 0 && cumulativeUsage.totalTokens < cursor.totalTokens;
|
|
1368
|
+
const increment = reset ? cumulativeUsage : subtractUsage(cumulativeUsage, cursor);
|
|
1369
|
+
if (Object.values(increment).some((value) => typeof value === "number" && value > 0))
|
|
1370
|
+
active2.usage = addUsage(active2.usage, increment);
|
|
1371
|
+
active2.usageCursor = cumulativeUsage;
|
|
1372
|
+
this.threadUsageTotal = cumulativeUsage;
|
|
1373
|
+
}
|
|
1374
|
+
if (record3(last)) {
|
|
1375
|
+
if (typeof last.totalTokens === "number") active2.contextTokens = last.totalTokens;
|
|
1376
|
+
}
|
|
1377
|
+
if (typeof params.tokenUsage.modelContextWindow === "number")
|
|
1378
|
+
active2.contextWindow = params.tokenUsage.modelContextWindow;
|
|
1379
|
+
} else if (method === "model/rerouted") {
|
|
1380
|
+
active2.actualModel = typeof params.toModel === "string" ? params.toModel : active2.actualModel;
|
|
1381
|
+
active2.failureCode = "CODEX_MODEL_UNAVAILABLE";
|
|
1382
|
+
} else if (method === "turn/completed" && record3(params.turn)) this.complete(params.turn);
|
|
1383
|
+
else if (method === "error" && params.willRetry !== true)
|
|
1384
|
+
active2.failureCode = this.nativeErrorCode(params.error);
|
|
1385
|
+
else if (method === "item/agentMessage/delta" && typeof params.itemId === "string" && typeof params.delta === "string") {
|
|
1386
|
+
const previous = active2.messages.get(params.itemId);
|
|
1387
|
+
if (previous === void 0)
|
|
1388
|
+
this.event({
|
|
1389
|
+
type: "message_start",
|
|
1390
|
+
message: { role: "assistant", content: [{ type: "text", text: "" }] }
|
|
1391
|
+
});
|
|
1392
|
+
const text2 = (previous ?? "") + params.delta;
|
|
1393
|
+
active2.messages.set(params.itemId, text2);
|
|
1394
|
+
this.event({
|
|
1395
|
+
type: "message_update",
|
|
1396
|
+
message: { role: "assistant", content: [{ type: "text", text: text2 }] },
|
|
1397
|
+
_textDelta: params.delta
|
|
1398
|
+
});
|
|
1399
|
+
} else if ((method === "item/started" || method === "item/completed") && record3(params.item) && typeof params.item.id === "string") {
|
|
1400
|
+
this.item(
|
|
1401
|
+
params.item,
|
|
1402
|
+
method === "item/completed"
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
item(item, completed) {
|
|
1407
|
+
const active2 = this.active;
|
|
1408
|
+
if (item.type === "fileChange") {
|
|
1409
|
+
active2.fileChanges.delete(item.id);
|
|
1410
|
+
if (!completed && Array.isArray(item.changes) && item.changes.length <= 16 && active2.fileChanges.size < 16) {
|
|
1411
|
+
const snapshot = JSON.stringify(item.changes);
|
|
1412
|
+
if (snapshot.length <= 65536) active2.fileChanges.set(item.id, snapshot);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
if (item.type === "agentMessage") {
|
|
1416
|
+
if (completed && typeof item.text === "string") {
|
|
1417
|
+
const previous = active2.messages.get(item.id) ?? "";
|
|
1418
|
+
if (!active2.messages.has(item.id))
|
|
1419
|
+
this.event({
|
|
1420
|
+
type: "message_start",
|
|
1421
|
+
message: {
|
|
1422
|
+
role: "assistant",
|
|
1423
|
+
content: [{ type: "text", text: "" }]
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1426
|
+
if (item.text.startsWith(previous) && item.text.length > previous.length)
|
|
1427
|
+
this.event({
|
|
1428
|
+
type: "message_update",
|
|
1429
|
+
message: {
|
|
1430
|
+
role: "assistant",
|
|
1431
|
+
content: [{ type: "text", text: item.text }]
|
|
1432
|
+
},
|
|
1433
|
+
_textDelta: item.text.slice(previous.length)
|
|
1434
|
+
});
|
|
1435
|
+
active2.messages.set(item.id, item.text);
|
|
1436
|
+
}
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
const name = item.type === "commandExecution" ? "Bash" : item.type === "fileChange" ? "ApplyPatch" : item.type === "mcpToolCall" ? `mcp__${item.server}__${item.tool}` : void 0;
|
|
1440
|
+
if (!name) return;
|
|
1441
|
+
if (!active2.toolItems.has(item.id)) {
|
|
1442
|
+
if (item.type === "mcpToolCall" && item.server === this.tools?.diagnostics?.serverName)
|
|
1443
|
+
this.mcpDiagnostics.nativeMcpToolItemCount = Math.min(
|
|
1444
|
+
512,
|
|
1445
|
+
this.mcpDiagnostics.nativeMcpToolItemCount + 1
|
|
1446
|
+
);
|
|
1447
|
+
active2.toolItems.add(item.id);
|
|
1448
|
+
this.event({
|
|
1449
|
+
type: "tool_call",
|
|
1450
|
+
name,
|
|
1451
|
+
tool: { name },
|
|
1452
|
+
toolCallId: item.id,
|
|
1453
|
+
input: item.type === "mcpToolCall" ? item.arguments : item.type === "commandExecution" ? { command: item.command } : item.type === "fileChange" ? { changes: item.changes } : {}
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
if (completed && !active2.completedToolItems.has(item.id)) {
|
|
1457
|
+
active2.completedToolItems.add(item.id);
|
|
1458
|
+
active2.toolResults.push({
|
|
1459
|
+
role: "tool",
|
|
1460
|
+
toolName: name,
|
|
1461
|
+
content: "",
|
|
1462
|
+
data: item.type === "mcpToolCall" ? {
|
|
1463
|
+
content: item.result?.content,
|
|
1464
|
+
structuredContent: item.result?.structuredContent,
|
|
1465
|
+
status: item.status
|
|
1466
|
+
} : item.type === "commandExecution" ? {
|
|
1467
|
+
output: item.aggregatedOutput,
|
|
1468
|
+
exitCode: item.exitCode,
|
|
1469
|
+
status: item.status
|
|
1470
|
+
} : item.type === "fileChange" ? { changes: item.changes, status: item.status } : {},
|
|
1471
|
+
isError: "status" in item && item.status === "failed"
|
|
1472
|
+
});
|
|
1473
|
+
this.event({ type: "tool_execution_end", toolName: name });
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
complete(turn) {
|
|
1477
|
+
if (turn.status === "completed") this.finishTurn(this.active?.failureCode);
|
|
1478
|
+
else if (turn.status === "interrupted") this.finishTurn("CODEX_TURN_INTERRUPTED");
|
|
1479
|
+
else if (turn.status === "failed")
|
|
1480
|
+
this.finishTurn(this.active?.failureCode ?? this.nativeErrorCode(turn.error));
|
|
1481
|
+
}
|
|
1482
|
+
nativeErrorCode(error) {
|
|
1483
|
+
const info = record3(error) ? error.codexErrorInfo : void 0;
|
|
1484
|
+
if (info === "unauthorized") return "CODEX_RECONNECT_REQUIRED";
|
|
1485
|
+
if (info === "usageLimitExceeded" || info === "rateLimitExceeded" || info === "serverOverloaded")
|
|
1486
|
+
return "CODEX_RATE_LIMITED";
|
|
1487
|
+
if (record3(info) && [
|
|
1488
|
+
"httpConnectionFailed",
|
|
1489
|
+
"responseStreamConnectionFailed",
|
|
1490
|
+
"responseStreamDisconnected",
|
|
1491
|
+
"responseTooManyFailedAttempts"
|
|
1492
|
+
].some((key) => key in info))
|
|
1493
|
+
return "CODEX_NETWORK_UNAVAILABLE";
|
|
1494
|
+
return "CODEX_RUNTIME_UNSUPPORTED";
|
|
1495
|
+
}
|
|
1496
|
+
observeMcp(beforeTurn) {
|
|
1497
|
+
const count = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? Math.min(512, value) : 0;
|
|
1498
|
+
let listed = 0;
|
|
1499
|
+
let called = 0;
|
|
1500
|
+
try {
|
|
1501
|
+
const counts = this.tools?.diagnostics?.requestCounts();
|
|
1502
|
+
listed = count(counts?.toolsListTotal);
|
|
1503
|
+
called = count(counts?.toolsCallTotal);
|
|
1504
|
+
} catch {
|
|
1505
|
+
}
|
|
1506
|
+
if (beforeTurn) {
|
|
1507
|
+
this.mcpDiagnostics.startupBeforeTurn = this.mcpStartup;
|
|
1508
|
+
this.mcpDiagnostics.toolsListBeforeTurn = listed;
|
|
1509
|
+
}
|
|
1510
|
+
this.mcpDiagnostics.startupAtEnd = this.mcpStartup;
|
|
1511
|
+
this.mcpDiagnostics.toolsListTotal = Math.max(this.mcpDiagnostics.toolsListBeforeTurn, listed);
|
|
1512
|
+
this.mcpDiagnostics.toolsCallTotal = beforeTurn ? called : Math.max(this.mcpDiagnostics.toolsCallTotal, called);
|
|
1513
|
+
this.publishMcp(this.mcpDiagnostics);
|
|
1514
|
+
}
|
|
1515
|
+
observeClosedMcp(tools, snapshot) {
|
|
1516
|
+
try {
|
|
1517
|
+
const counts = tools.diagnostics?.requestCounts();
|
|
1518
|
+
if (!counts || ![counts.toolsListTotal, counts.toolsCallTotal].every(
|
|
1519
|
+
(value) => Number.isSafeInteger(value) && value >= 0
|
|
1520
|
+
))
|
|
1521
|
+
return;
|
|
1522
|
+
snapshot.toolsListTotal = Math.max(
|
|
1523
|
+
snapshot.toolsListTotal,
|
|
1524
|
+
Math.min(512, counts.toolsListTotal)
|
|
1525
|
+
);
|
|
1526
|
+
snapshot.toolsCallTotal = Math.max(
|
|
1527
|
+
snapshot.toolsCallTotal,
|
|
1528
|
+
Math.min(512, counts.toolsCallTotal)
|
|
1529
|
+
);
|
|
1530
|
+
this.publishMcp(snapshot);
|
|
1531
|
+
} catch {
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
publishMcp(snapshot) {
|
|
1535
|
+
try {
|
|
1536
|
+
void Promise.resolve(
|
|
1537
|
+
this.options?.onNativeMcpDiagnostics?.(Object.freeze({ ...snapshot }))
|
|
1538
|
+
).catch(() => {
|
|
1539
|
+
});
|
|
1540
|
+
} catch {
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
finishTurn(code) {
|
|
1544
|
+
const active2 = this.active;
|
|
1545
|
+
if (!active2) return;
|
|
1546
|
+
this.active = void 0;
|
|
1547
|
+
this.observeMcp(false);
|
|
1548
|
+
clearTimeout(active2.timer);
|
|
1549
|
+
active2.controller.abort();
|
|
1550
|
+
active2.fileChanges.clear();
|
|
1551
|
+
active2.readyNow();
|
|
1552
|
+
for (const text2 of active2.messages.values())
|
|
1553
|
+
this.event({
|
|
1554
|
+
type: "message_end",
|
|
1555
|
+
message: { role: "assistant", content: [{ type: "text", text: text2 }] }
|
|
1556
|
+
});
|
|
1557
|
+
this.event({ type: "turn_end", toolResults: active2.toolResults });
|
|
1558
|
+
const modelUsage = active2.usage ? {
|
|
1559
|
+
inputTokens: active2.usage.inputTokens,
|
|
1560
|
+
outputTokens: active2.usage.outputTokens,
|
|
1561
|
+
cacheReadTokens: active2.usage.cacheReadTokens,
|
|
1562
|
+
...typeof active2.contextWindow === "number" ? { contextWindow: active2.contextWindow } : {}
|
|
1563
|
+
} : void 0;
|
|
1564
|
+
const usage = {
|
|
1565
|
+
...active2.usage ? { tokens: active2.usage } : {},
|
|
1566
|
+
...modelUsage ? { modelUsage: { [active2.actualModel]: modelUsage } } : {},
|
|
1567
|
+
...typeof active2.contextTokens === "number" ? { contextTokens: active2.contextTokens } : {},
|
|
1568
|
+
...typeof active2.contextWindow === "number" ? { contextWindow: active2.contextWindow } : {}
|
|
1569
|
+
};
|
|
1570
|
+
if (code)
|
|
1571
|
+
this.event({
|
|
1572
|
+
type: "error",
|
|
1573
|
+
error: code,
|
|
1574
|
+
fatal: code !== "CODEX_TURN_INTERRUPTED",
|
|
1575
|
+
detail: {
|
|
1576
|
+
message: code,
|
|
1577
|
+
category: code === "CODEX_TURN_INTERRUPTED" ? "process" : code === "CODEX_RATE_LIMITED" ? "rate_limit" : code === "CODEX_NETWORK_UNAVAILABLE" ? "network" : code === "CODEX_AUTH_UNAVAILABLE" || code === "CODEX_RECONNECT_REQUIRED" ? "auth" : "config",
|
|
1578
|
+
retryable: false
|
|
1579
|
+
},
|
|
1580
|
+
...usage
|
|
1581
|
+
});
|
|
1582
|
+
else
|
|
1583
|
+
this.event({
|
|
1584
|
+
type: "result",
|
|
1585
|
+
subtype: "success",
|
|
1586
|
+
summary: [...active2.messages.values()].join("\n"),
|
|
1587
|
+
...usage
|
|
1588
|
+
});
|
|
1589
|
+
this.event({ type: "agent_end" });
|
|
1590
|
+
active2.finish();
|
|
1591
|
+
}
|
|
1592
|
+
event(event) {
|
|
1593
|
+
try {
|
|
1594
|
+
this.emit("agent-event", event);
|
|
1595
|
+
} catch {
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
var CODEX_WIRE_API = {
|
|
1600
|
+
"openai-codex-responses": "responses",
|
|
1601
|
+
"openai-responses": "responses",
|
|
1602
|
+
"azure-openai-responses": "responses"
|
|
1603
|
+
};
|
|
1604
|
+
var CODEX_RESERVED_PROVIDER_IDS = /* @__PURE__ */ new Set(["openai", "ollama", "lmstudio"]);
|
|
1605
|
+
function codexProviderTableId(id) {
|
|
1606
|
+
return CODEX_RESERVED_PROVIDER_IDS.has(id) ? `skaile-${id}` : id;
|
|
1607
|
+
}
|
|
1608
|
+
var ENV_DELIVERED_CREDENTIALS = {
|
|
1609
|
+
api_key_header: true,
|
|
1610
|
+
bearer: true,
|
|
1611
|
+
command: true
|
|
1612
|
+
};
|
|
1613
|
+
function selectionNamesEntry(input, selected, registry) {
|
|
1614
|
+
if (selected.role && selected.modelId !== void 0) return true;
|
|
1615
|
+
const selector = input.modelRef?.trim() || input.model?.trim() || "";
|
|
1616
|
+
if (selector.startsWith(`${selected.providerId}/`)) return true;
|
|
1617
|
+
const entry = registryEntries(registry).find(([id]) => id === selected.providerId)?.[1];
|
|
1618
|
+
const modelId = selected.modelId;
|
|
1619
|
+
return modelId !== void 0 && (entry?.model_map?.[modelId] !== void 0 || entry?.capabilities?.[modelId] !== void 0);
|
|
1620
|
+
}
|
|
1621
|
+
function renderCodexProviderTable(id, entry) {
|
|
1622
|
+
if (!isEntryEnabled(entry)) return { skipped: "entry is disabled" };
|
|
1623
|
+
if (entry.kind !== "llm" && entry.kind !== "embed")
|
|
1624
|
+
return { skipped: `kind '${entry.kind}' is not a model endpoint` };
|
|
1625
|
+
const llm = entry;
|
|
1626
|
+
const preset = entry.preset ? getProviderPreset(entry.preset) : void 0;
|
|
1627
|
+
const surface = llm.surface ?? preset?.surface;
|
|
1628
|
+
const wireApi = surface ? CODEX_WIRE_API[surface] : void 0;
|
|
1629
|
+
if (!wireApi)
|
|
1630
|
+
return {
|
|
1631
|
+
skipped: surface ? `codex has no wire_api for surface '${surface}'` : "entry declares no surface"
|
|
1632
|
+
};
|
|
1633
|
+
const credentialKind = entry.credential?.kind;
|
|
1634
|
+
const table = { name: entry.label ?? id };
|
|
1635
|
+
const baseUrl = resolveEntryBaseUrl(entry) ?? preset?.base_url;
|
|
1636
|
+
if (baseUrl) table.base_url = baseUrl;
|
|
1637
|
+
if (credentialKind && ENV_DELIVERED_CREDENTIALS[credentialKind])
|
|
1638
|
+
table.env_key = providerCredentialEnvName(id);
|
|
1639
|
+
table.wire_api = wireApi;
|
|
1640
|
+
if (credentialKind === "oauth") table.requires_openai_auth = true;
|
|
1641
|
+
if (llm.query_params && Object.keys(llm.query_params).length > 0)
|
|
1642
|
+
table.query_params = { ...llm.query_params };
|
|
1643
|
+
if (entry.headers && Object.keys(entry.headers).length > 0)
|
|
1644
|
+
table.http_headers = { ...entry.headers };
|
|
1645
|
+
return { table };
|
|
1646
|
+
}
|
|
1647
|
+
function buildCodexConfigDocument(input) {
|
|
1648
|
+
const document = { model_providers: {} };
|
|
1649
|
+
const skipped = [];
|
|
1650
|
+
const entries = input.registry ? registryEntries(input.registry) : [];
|
|
1651
|
+
for (const [id, entry] of entries) {
|
|
1652
|
+
const rendered = renderCodexProviderTable(id, entry);
|
|
1653
|
+
if ("skipped" in rendered) skipped.push({ id, reason: rendered.skipped });
|
|
1654
|
+
else document.model_providers[codexProviderTableId(id)] = rendered.table;
|
|
1655
|
+
}
|
|
1656
|
+
if (!input.registry) return { document, skipped, warnings: [] };
|
|
1657
|
+
const selected = parseModelRef(
|
|
1658
|
+
input.modelRef?.trim() || input.model?.trim() || "",
|
|
1659
|
+
input.registry,
|
|
1660
|
+
input.roles
|
|
1661
|
+
);
|
|
1662
|
+
if (!selected) return { document, skipped, warnings: [] };
|
|
1663
|
+
const warnings = selected.warnings;
|
|
1664
|
+
const tableId = codexProviderTableId(selected.providerId);
|
|
1665
|
+
if (!document.model_providers[tableId]) {
|
|
1666
|
+
const reason = skipped.find((s) => s.id === selected.providerId)?.reason;
|
|
1667
|
+
if (reason && selectionNamesEntry(input, selected, input.registry)) {
|
|
1668
|
+
return {
|
|
1669
|
+
document,
|
|
1670
|
+
skipped,
|
|
1671
|
+
warnings,
|
|
1672
|
+
unaddressable: { id: selected.providerId, reason }
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
return { document, skipped, warnings };
|
|
1676
|
+
}
|
|
1677
|
+
document.model_provider = tableId;
|
|
1678
|
+
if (selected.providerModelId) document.model = selected.providerModelId;
|
|
1679
|
+
return {
|
|
1680
|
+
document,
|
|
1681
|
+
providerId: selected.providerId,
|
|
1682
|
+
...selected.providerModelId ? { model: selected.providerModelId } : {},
|
|
1683
|
+
skipped,
|
|
1684
|
+
warnings
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
function renderCodexConfigToml(document) {
|
|
1688
|
+
const ordered = {};
|
|
1689
|
+
if (document.model_provider) ordered.model_provider = document.model_provider;
|
|
1690
|
+
if (document.model) ordered.model = document.model;
|
|
1691
|
+
ordered.model_providers = document.model_providers;
|
|
1692
|
+
return `${stringify(ordered)}
|
|
1693
|
+
`;
|
|
1694
|
+
}
|
|
1695
|
+
function codexSessionHome(config) {
|
|
1696
|
+
const key = config.sessionId && /^[A-Za-z0-9._-]{1,128}$/.test(config.sessionId) ? config.sessionId : createHash("sha256").update(config.sessionId ?? config.cwd).digest("hex").slice(0, 32);
|
|
1697
|
+
return join(globalSkaileDir(), "codex", key);
|
|
1698
|
+
}
|
|
1699
|
+
function writeCodexConfigToml(home, document) {
|
|
1700
|
+
const path = join(home, "config.toml");
|
|
1701
|
+
mkdirSync(home, { recursive: true, mode: 448 });
|
|
1702
|
+
if (Object.keys(document.model_providers).length === 0) {
|
|
1703
|
+
rmSync(path, { force: true });
|
|
1704
|
+
return void 0;
|
|
1705
|
+
}
|
|
1706
|
+
const temporary = join(home, "config.toml.new");
|
|
1707
|
+
rmSync(temporary, { force: true });
|
|
1708
|
+
writeFileSync(temporary, renderCodexConfigToml(document), { mode: 384, flag: "wx" });
|
|
1709
|
+
renameSync(temporary, path);
|
|
1710
|
+
return path;
|
|
1711
|
+
}
|
|
1712
|
+
function isChatgptLogin(file) {
|
|
1713
|
+
if (!file) return false;
|
|
1714
|
+
try {
|
|
1715
|
+
const doc = JSON.parse(readFileSync(file, "utf8"));
|
|
1716
|
+
return doc.auth_mode === "chatgpt";
|
|
1717
|
+
} catch {
|
|
1718
|
+
return false;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
function codexLoginFile(env) {
|
|
1722
|
+
const home = env.CODEX_HOME?.trim() || join(env.HOME || homedir(), ".codex");
|
|
1723
|
+
const file = join(home, "auth.json");
|
|
1724
|
+
return existsSync(file) ? resolve(file) : void 0;
|
|
1725
|
+
}
|
|
1726
|
+
function usesCodexOwnAuth(build, env, loginFile) {
|
|
1727
|
+
if (build.providerId !== "openai") return false;
|
|
1728
|
+
const table = build.document.model_providers[codexProviderTableId("openai")];
|
|
1729
|
+
if (!table?.env_key || table.requires_openai_auth || env[table.env_key]) return false;
|
|
1730
|
+
return Boolean(env.OPENAI_API_KEY || env.CODEX_API_KEY || loginFile);
|
|
1731
|
+
}
|
|
1732
|
+
function codexLoginFallbackNotice(session, env) {
|
|
1733
|
+
if (!session.registry) return void 0;
|
|
1734
|
+
const build = buildCodexConfigDocument({
|
|
1735
|
+
registry: session.registry,
|
|
1736
|
+
...session.modelRef ? { modelRef: session.modelRef } : {}
|
|
1737
|
+
});
|
|
1738
|
+
if (!usesCodexOwnAuth(build, env, codexLoginFile(env))) return void 0;
|
|
1739
|
+
const unresolved = unresolvedProviderCredentials(session.registry, {
|
|
1740
|
+
env,
|
|
1741
|
+
providerId: "openai"
|
|
1742
|
+
})[0];
|
|
1743
|
+
return `provider 'openai' is not used: ${unresolved?.reason ?? "its credential is not set"}; codex uses its own login or OPENAI_API_KEY instead (check with \`skaile providers test openai\`)`;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// bridge/src/codex/friendly-error.ts
|
|
1747
|
+
function unwrapErrorBody(raw) {
|
|
1748
|
+
const start = raw.indexOf("{");
|
|
1749
|
+
if (start < 0) return raw;
|
|
1750
|
+
try {
|
|
1751
|
+
const body = JSON.parse(raw.slice(start));
|
|
1752
|
+
const inner = typeof body.error === "object" ? body.error?.message : body.error;
|
|
1753
|
+
const text2 = inner ?? body.message;
|
|
1754
|
+
if (typeof text2 === "string" && text2.length > 0) return text2;
|
|
1755
|
+
} catch {
|
|
1756
|
+
}
|
|
1757
|
+
return raw;
|
|
1758
|
+
}
|
|
1759
|
+
function nativeCause(raw) {
|
|
1760
|
+
const reconnecting = /^Reconnecting\.\.\.\s*\d+\/\d+\s*\(([\s\S]*)\)\s*$/.exec(raw.trim());
|
|
1761
|
+
const reason = reconnecting ? reconnecting[1] ?? "" : unwrapErrorBody(raw);
|
|
1762
|
+
const line = reason.split(/[{\n]/, 1)[0]?.replace(/[\s:,.]+$/, "") ?? "";
|
|
1763
|
+
return line.length > 160 ? `${line.slice(0, 157)}...` : line;
|
|
1764
|
+
}
|
|
1765
|
+
function friendlyCodexError(raw, context = {}) {
|
|
1766
|
+
const text2 = unwrapErrorBody(raw);
|
|
1767
|
+
const chatgpt = /'([^']+)' model is not supported when using Codex with a ChatGPT account/i.exec(
|
|
1768
|
+
text2
|
|
1769
|
+
);
|
|
1770
|
+
if (chatgpt) {
|
|
1771
|
+
return {
|
|
1772
|
+
message: `codex harness: the model '${chatgpt[1]}' is not available with a ChatGPT login.`,
|
|
1773
|
+
category: "model",
|
|
1774
|
+
hint: "Update @openai/codex-sdk to the 0.16x line, pass --model with a model your login allows, or set OPENAI_API_KEY."
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
if (/newer version of codex|requires a newer version|update codex/i.test(text2)) {
|
|
1778
|
+
return {
|
|
1779
|
+
message: `codex harness: the selected model needs a newer Codex than the installed @openai/codex-sdk.`,
|
|
1780
|
+
category: "model",
|
|
1781
|
+
hint: "Update @openai/codex-sdk to the 0.16x line (npm i @openai/codex-sdk@latest)."
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
if (/invalid_api_key|incorrect api key|\b401\b[^.]*unauthorized|unauthorized[^.]*\b401\b/i.test(raw)) {
|
|
1785
|
+
if (context.chatgptLogin) {
|
|
1786
|
+
const cause = nativeCause(raw);
|
|
1787
|
+
return {
|
|
1788
|
+
message: `codex harness: the codex login was rejected (401)${cause ? `: ${cause}` : ""}.`,
|
|
1789
|
+
category: "auth",
|
|
1790
|
+
hint: "Run `codex login` to sign in again."
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
return {
|
|
1794
|
+
message: "codex harness: the API key was rejected (401).",
|
|
1795
|
+
category: "auth",
|
|
1796
|
+
hint: "Check OPENAI_API_KEY (or sign in again with `codex login`)."
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
const reconnecting = /^Reconnecting\.\.\.\s*\d+\/\d+\s*\(([\s\S]*)\)\s*$/.exec(raw.trim());
|
|
1800
|
+
if (reconnecting) {
|
|
1801
|
+
const reason = (reconnecting[1] ?? "").split(/[{\n]/, 1)[0]?.replace(/[\s:,]+$/, "") ?? "";
|
|
1802
|
+
return {
|
|
1803
|
+
message: `codex harness: could not reach the model endpoint${reason ? ` (${reason})` : ""}.`,
|
|
1804
|
+
category: "network",
|
|
1805
|
+
hint: "Check the network and the provider's status, then run again."
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
return null;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// bridge/src/harnesses/codex.ts
|
|
1812
|
+
function buildCodexChildEnv(parent, codexHome) {
|
|
1813
|
+
const out = {};
|
|
1814
|
+
const explicit = [
|
|
1815
|
+
"PATH",
|
|
1816
|
+
"HOME",
|
|
1817
|
+
"USER",
|
|
1818
|
+
"HOSTNAME",
|
|
1819
|
+
"SKAILE_SESSION_ID",
|
|
1820
|
+
...RUNNER_AGENT_ENV_KEYS
|
|
1821
|
+
];
|
|
1822
|
+
for (const key of explicit) {
|
|
1823
|
+
const v = parent[key];
|
|
1824
|
+
if (v !== void 0) out[key] = v;
|
|
1825
|
+
}
|
|
1826
|
+
for (const [key, value] of Object.entries(parent)) {
|
|
1827
|
+
if (key.startsWith("PI_") && typeof value === "string") out[key] = value;
|
|
1828
|
+
}
|
|
1829
|
+
for (const key of ["OPENAI_API_KEY", "CODEX_API_KEY"]) {
|
|
1830
|
+
const v = parent[key];
|
|
1831
|
+
if (v) out[key] = v;
|
|
1832
|
+
}
|
|
1833
|
+
out.CODEX_HOME = codexHome;
|
|
1834
|
+
return out;
|
|
1835
|
+
}
|
|
1836
|
+
function linkCodexLogin(codexHome, loginFile) {
|
|
1837
|
+
const target = join(codexHome, "auth.json");
|
|
1838
|
+
mkdirSync(codexHome, { recursive: true, mode: 448 });
|
|
1839
|
+
rmSync(target, { force: true });
|
|
1840
|
+
try {
|
|
1841
|
+
symlinkSync(loginFile, target);
|
|
1842
|
+
} catch {
|
|
1843
|
+
copyFileSync(loginFile, target);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
function missingCodexCredential(build, env, loginFile, unresolved) {
|
|
1847
|
+
const table = build.providerId ? build.document.model_providers[codexProviderTableId(build.providerId)] : void 0;
|
|
1848
|
+
if (table) {
|
|
1849
|
+
if (table.requires_openai_auth) return void 0;
|
|
1850
|
+
if (!table.env_key || env[table.env_key]) return void 0;
|
|
1851
|
+
return `codex harness: the AI provider entry '${build.providerId}' has no credential${unresolved ? ` (${unresolved})` : ""}; export the variable its credential names (see \`skaile providers test ${build.providerId}\`)`;
|
|
1852
|
+
}
|
|
1853
|
+
if (env.OPENAI_API_KEY || env.CODEX_API_KEY || loginFile) return void 0;
|
|
1854
|
+
return "codex harness needs OPENAI_API_KEY, or the codex CLI's ChatGPT login (run `codex login`; ~/.codex/auth.json), or an AI provider entry with a credential";
|
|
1855
|
+
}
|
|
1856
|
+
function resolveCodexAuth(build, env, registry, loginEnv) {
|
|
1857
|
+
const hasKey = Boolean(env.OPENAI_API_KEY || env.CODEX_API_KEY);
|
|
1858
|
+
const loginFile = hasKey ? void 0 : codexLoginFile(loginEnv);
|
|
1859
|
+
const ownAuth = usesCodexOwnAuth(build, env, loginFile);
|
|
1860
|
+
if (ownAuth) {
|
|
1861
|
+
delete build.document.model_provider;
|
|
1862
|
+
delete build.document.model_providers[codexProviderTableId("openai")];
|
|
1863
|
+
}
|
|
1864
|
+
const unresolved = build.providerId && registry ? unresolvedProviderCredentials(registry, {
|
|
1865
|
+
env: { ...process.env, ...env },
|
|
1866
|
+
providerId: build.providerId
|
|
1867
|
+
})[0]?.reason : void 0;
|
|
1868
|
+
return { loginFile, missing: missingCodexCredential(build, env, loginFile, unresolved), ownAuth };
|
|
1869
|
+
}
|
|
1870
|
+
function checkCodexCredential(input) {
|
|
1871
|
+
const build = buildCodexConfigDocument({
|
|
1872
|
+
registry: input.registry,
|
|
1873
|
+
...input.model ? { modelRef: input.model, model: input.model } : {},
|
|
1874
|
+
...input.roles ? { roles: input.roles } : {}
|
|
1875
|
+
});
|
|
1876
|
+
if (build.unaddressable) return void 0;
|
|
1877
|
+
const env = buildCodexChildEnv(process.env, "");
|
|
1878
|
+
Object.assign(
|
|
1879
|
+
env,
|
|
1880
|
+
registryCredentialEnv(input.registry ?? {}, { env: { ...process.env, ...env } })
|
|
1881
|
+
);
|
|
1882
|
+
stripRunnerPrivateEnv(env);
|
|
1883
|
+
return resolveCodexAuth(build, env, input.registry, process.env).missing;
|
|
1884
|
+
}
|
|
1885
|
+
var CodexHarness = class extends AgentHarness {
|
|
1886
|
+
harnessInfo = {
|
|
1887
|
+
id: "codex",
|
|
1888
|
+
name: "OpenAI Codex",
|
|
1889
|
+
modelAgnostic: false,
|
|
1890
|
+
supportsInBandAbort: true,
|
|
1891
|
+
capabilities: BUILTIN_HARNESS_CATALOG.codex?.capabilities
|
|
1892
|
+
};
|
|
1893
|
+
log = getBridgeLogger("codex");
|
|
1894
|
+
config;
|
|
1895
|
+
reconfig = new ReconfigureLedger({ config: () => this.config });
|
|
1896
|
+
thread = null;
|
|
1897
|
+
running = false;
|
|
1898
|
+
abortController = null;
|
|
1899
|
+
pendingToolResults = [];
|
|
1900
|
+
/** The text of the last top-level `error` of the running turn: the cause when the stream ends with no `turn.*` event. */
|
|
1901
|
+
lastStreamError = null;
|
|
1902
|
+
activeAssistantItemId = null;
|
|
1903
|
+
/** The run signs in with codex's ChatGPT login, not an API key: a 401 means the login lapsed. */
|
|
1904
|
+
chatgptLogin = false;
|
|
1905
|
+
activeAssistantText = "";
|
|
1906
|
+
_runtimeSessionId;
|
|
1907
|
+
/** Token usage from the most recent completed turn. */
|
|
1908
|
+
_lastTokens = null;
|
|
1909
|
+
constructor(config) {
|
|
1910
|
+
super();
|
|
1911
|
+
if (isManagedCodexConfig(config)) throw new Error("CODEX_MANAGED_REQUIRES_FACTORY");
|
|
1912
|
+
this.config = config;
|
|
1913
|
+
}
|
|
1914
|
+
get runtimeSessionId() {
|
|
1915
|
+
return this._runtimeSessionId;
|
|
1916
|
+
}
|
|
1917
|
+
async dispose() {
|
|
1918
|
+
this.kill();
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* Store a model / thinking / effort change. The SDK thread is built from
|
|
1922
|
+
* `config` in {@link start}, so the next thread start (first prompt, or after
|
|
1923
|
+
* `resetSession()`) uses it; a thread already running keeps its options.
|
|
1924
|
+
*/
|
|
1925
|
+
reconfigure(patch) {
|
|
1926
|
+
return this.reconfig.reconfigure(patch);
|
|
1927
|
+
}
|
|
1928
|
+
getModel() {
|
|
1929
|
+
return this.config.model;
|
|
1930
|
+
}
|
|
1931
|
+
async start() {
|
|
1932
|
+
if (this.thread) return;
|
|
1933
|
+
let sdk;
|
|
1934
|
+
try {
|
|
1935
|
+
sdk = await loadHarnessSdk(
|
|
1936
|
+
"@openai/codex-sdk",
|
|
1937
|
+
this.config.cwd,
|
|
1938
|
+
() => import('@openai/codex-sdk'),
|
|
1939
|
+
"Codex SDK"
|
|
1940
|
+
);
|
|
1941
|
+
} catch (err) {
|
|
1942
|
+
this.log.error("codex SDK load failed", err);
|
|
1943
|
+
throw err;
|
|
1944
|
+
}
|
|
1945
|
+
const codexHome = codexSessionHome(this.config);
|
|
1946
|
+
const build = buildCodexConfigDocument({
|
|
1947
|
+
registry: this.config.registry,
|
|
1948
|
+
modelRef: this.config.modelRef,
|
|
1949
|
+
model: this.config.model,
|
|
1950
|
+
roles: this.config.roles
|
|
1951
|
+
});
|
|
1952
|
+
if (build.unaddressable) {
|
|
1953
|
+
const { id, reason } = build.unaddressable;
|
|
1954
|
+
const message = `AI provider '${id}' cannot be used with the codex harness: ${reason}. Pick a model of another provider.`;
|
|
1955
|
+
this.log.error("codex cannot address the selected AI provider", { provider: id, reason });
|
|
1956
|
+
throw Object.assign(new Error(message), { category: "config", hint: message });
|
|
1957
|
+
}
|
|
1958
|
+
for (const warning of build.warnings) this.log.warn("codex model selection", { warning });
|
|
1959
|
+
for (const entry of build.skipped)
|
|
1960
|
+
this.log.debug("codex cannot address an AI provider entry", {
|
|
1961
|
+
provider: entry.id,
|
|
1962
|
+
reason: entry.reason
|
|
1963
|
+
});
|
|
1964
|
+
const env = this.config.env ? { ...this.config.env, CODEX_HOME: codexHome } : buildCodexChildEnv(process.env, codexHome);
|
|
1965
|
+
Object.assign(
|
|
1966
|
+
env,
|
|
1967
|
+
registryCredentialEnv(this.config.registry ?? {}, { env: { ...process.env, ...env } })
|
|
1968
|
+
);
|
|
1969
|
+
stripRunnerPrivateEnv(env);
|
|
1970
|
+
const { loginFile, missing, ownAuth } = resolveCodexAuth(build, env, this.config.registry, {
|
|
1971
|
+
...process.env,
|
|
1972
|
+
...this.config.env
|
|
1973
|
+
});
|
|
1974
|
+
this.chatgptLogin = isChatgptLogin(loginFile);
|
|
1975
|
+
if (ownAuth) this.log.debug("codex uses its own auth: the openai entry has no credential");
|
|
1976
|
+
if (missing) {
|
|
1977
|
+
this.log.error("codex credential missing", { reason: missing });
|
|
1978
|
+
throw Object.assign(new Error(missing), { category: "config", hint: missing });
|
|
1979
|
+
}
|
|
1980
|
+
writeCodexConfigToml(codexHome, build.document);
|
|
1981
|
+
if (loginFile) linkCodexLogin(codexHome, loginFile);
|
|
1982
|
+
else rmSync(join(codexHome, "auth.json"), { force: true });
|
|
1983
|
+
const instructionsFile = this.writeInstructionsFile();
|
|
1984
|
+
const client = new sdk.Codex({
|
|
1985
|
+
apiKey: env.OPENAI_API_KEY,
|
|
1986
|
+
env: Object.fromEntries(Object.entries(env).filter(([, value]) => value != null)),
|
|
1987
|
+
...instructionsFile ? { config: { model_instructions_file: instructionsFile } } : {}
|
|
1988
|
+
});
|
|
1989
|
+
const codexOptions = this.config.harnessOptions?.codex;
|
|
1990
|
+
const threadOptions = {
|
|
1991
|
+
model: build.model ?? this.config.model,
|
|
1992
|
+
sandboxMode: codexOptions?.sandboxMode ?? "workspace-write",
|
|
1993
|
+
workingDirectory: this.config.cwd,
|
|
1994
|
+
skipGitRepoCheck: true,
|
|
1995
|
+
modelReasoningEffort: codexOptions?.reasoningEffort ?? "medium",
|
|
1996
|
+
networkAccessEnabled: codexOptions?.networkAccessEnabled,
|
|
1997
|
+
approvalPolicy: codexOptions?.approvalPolicy ?? "never",
|
|
1998
|
+
additionalDirectories: codexOptions?.additionalDirectories
|
|
1999
|
+
};
|
|
2000
|
+
this.thread = this.config.resumeSessionId ? client.resumeThread(this.config.resumeSessionId, threadOptions) : client.startThread(threadOptions);
|
|
2001
|
+
this._runtimeSessionId = this.thread.id ?? this.config.resumeSessionId;
|
|
2002
|
+
this.log.info("codex harness started", {
|
|
2003
|
+
model: this.config.model,
|
|
2004
|
+
resumed: Boolean(this.config.resumeSessionId),
|
|
2005
|
+
sessionId: this._runtimeSessionId
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
async prompt(message) {
|
|
2009
|
+
await this.start();
|
|
2010
|
+
if (!this.thread) throw new Error("Codex thread not initialized");
|
|
2011
|
+
if (this.running) throw new Error("Codex turn already in progress");
|
|
2012
|
+
this.running = true;
|
|
2013
|
+
this.pendingToolResults = [];
|
|
2014
|
+
this.activeAssistantItemId = null;
|
|
2015
|
+
this.activeAssistantText = "";
|
|
2016
|
+
this._lastTokens = null;
|
|
2017
|
+
this.abortController = new AbortController();
|
|
2018
|
+
let turnFinished = false;
|
|
2019
|
+
this.lastStreamError = null;
|
|
2020
|
+
this.log.debug("prompt issued", { length: message.length });
|
|
2021
|
+
try {
|
|
2022
|
+
const input = message;
|
|
2023
|
+
const { events } = await this.thread.runStreamed(input, {
|
|
2024
|
+
signal: this.abortController.signal
|
|
2025
|
+
});
|
|
2026
|
+
for await (const event of events) {
|
|
2027
|
+
if (turnFinished) {
|
|
2028
|
+
this.log.debug("codex event after the turn ended", { type: event.type });
|
|
2029
|
+
continue;
|
|
2030
|
+
}
|
|
2031
|
+
turnFinished = this.handleThreadEvent(event);
|
|
2032
|
+
}
|
|
2033
|
+
if (!turnFinished && this.lastStreamError !== null) {
|
|
2034
|
+
this.flushToolResults();
|
|
2035
|
+
const detail = this.classifyErrorMessage(this.lastStreamError);
|
|
2036
|
+
this.emit("agent-event", {
|
|
2037
|
+
type: "error",
|
|
2038
|
+
error: detail.message,
|
|
2039
|
+
detail
|
|
2040
|
+
});
|
|
2041
|
+
this.emit("agent-event", { type: "agent_end" });
|
|
2042
|
+
turnFinished = true;
|
|
2043
|
+
}
|
|
2044
|
+
} catch (err) {
|
|
2045
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
2046
|
+
if (error.name !== "AbortError") {
|
|
2047
|
+
this.log.error("prompt failed", error);
|
|
2048
|
+
if (!turnFinished) {
|
|
2049
|
+
const detail = this.classifyErrorMessage(error.message);
|
|
2050
|
+
this.emit("agent-event", {
|
|
2051
|
+
type: "error",
|
|
2052
|
+
error: detail.message,
|
|
2053
|
+
detail
|
|
2054
|
+
});
|
|
2055
|
+
this.emit("agent-event", { type: "agent_end" });
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
} finally {
|
|
2059
|
+
if (!turnFinished && this.activeAssistantItemId) {
|
|
2060
|
+
this.emit("agent-event", {
|
|
2061
|
+
type: "message_end",
|
|
2062
|
+
message: this.buildAssistantMessage(this.activeAssistantText)
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
this.activeAssistantItemId = null;
|
|
2066
|
+
this.activeAssistantText = "";
|
|
2067
|
+
this.abortController = null;
|
|
2068
|
+
this.running = false;
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
async abort() {
|
|
2072
|
+
this.abortController?.abort();
|
|
2073
|
+
}
|
|
2074
|
+
get isRunning() {
|
|
2075
|
+
return this.running;
|
|
2076
|
+
}
|
|
2077
|
+
kill() {
|
|
2078
|
+
this.abortController?.abort();
|
|
2079
|
+
this.running = false;
|
|
2080
|
+
this.abortController = null;
|
|
2081
|
+
this.log.info("codex harness killed");
|
|
2082
|
+
}
|
|
2083
|
+
/**
|
|
2084
|
+
* Discover models from the endpoint this session actually addresses.
|
|
2085
|
+
*
|
|
2086
|
+
* The registry entry codex would talk to supplies both the base URL and the
|
|
2087
|
+
* credential, so a session pointed at a gateway lists that gateway's models
|
|
2088
|
+
* instead of api.openai.com's. A `platform:` reference the session boundary
|
|
2089
|
+
* already resolved is picked up from `SKAILE_AIP_<ID>_KEY` in the session
|
|
2090
|
+
* env. Without a registry entry codex can address, an ambient
|
|
2091
|
+
* `OPENAI_API_KEY` is the last resort.
|
|
2092
|
+
*/
|
|
2093
|
+
async listModels() {
|
|
2094
|
+
const selected = this.selectedProviderEntry();
|
|
2095
|
+
const sessionEnv = { ...process.env, ...this.config.env };
|
|
2096
|
+
const apiKey = selected ? entryCredentialValue(selected.id, selected.entry, { env: sessionEnv }) : sessionEnv.OPENAI_API_KEY;
|
|
2097
|
+
if (!apiKey) return [];
|
|
2098
|
+
const baseUrl = selected ? resolveEntryBaseUrl(selected.entry) : void 0;
|
|
2099
|
+
const result = await fetchProviderModels("openai", apiKey, baseUrl ? { baseUrl } : {});
|
|
2100
|
+
return result.ok ? result.models : [];
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* The registry entry codex addresses this session — the same selection the
|
|
2104
|
+
* rendered `config.toml` names in `model_provider`.
|
|
2105
|
+
*/
|
|
2106
|
+
selectedProviderEntry() {
|
|
2107
|
+
const registry = this.config.registry;
|
|
2108
|
+
if (!registry) return void 0;
|
|
2109
|
+
const { providerId } = buildCodexConfigDocument({
|
|
2110
|
+
registry,
|
|
2111
|
+
modelRef: this.config.modelRef,
|
|
2112
|
+
model: this.config.model,
|
|
2113
|
+
roles: this.config.roles
|
|
2114
|
+
});
|
|
2115
|
+
if (!providerId) return void 0;
|
|
2116
|
+
const match = registryEntries(registry).find(([id]) => id === providerId);
|
|
2117
|
+
return match ? { id: match[0], entry: match[1] } : void 0;
|
|
2118
|
+
}
|
|
2119
|
+
async resetSession() {
|
|
2120
|
+
this.kill();
|
|
2121
|
+
this.config = { ...this.config, resumeSessionId: void 0 };
|
|
2122
|
+
this.thread = null;
|
|
2123
|
+
this._runtimeSessionId = void 0;
|
|
2124
|
+
this._lastTokens = null;
|
|
2125
|
+
}
|
|
2126
|
+
getTokenUsage() {
|
|
2127
|
+
return this._lastTokens;
|
|
2128
|
+
}
|
|
2129
|
+
/**
|
|
2130
|
+
* Write a combined instructions file for Codex CLI's `model_instructions_file` config.
|
|
2131
|
+
* Combines the agent's pre-rendered identity (`.codex/skaile-agents/<agentName>.md`, written
|
|
2132
|
+
* by `skaile install`, one file per agent instance) with runtime context
|
|
2133
|
+
* (resources, environment, prompt-extensions).
|
|
2134
|
+
*/
|
|
2135
|
+
writeInstructionsFile() {
|
|
2136
|
+
const parts = [];
|
|
2137
|
+
if (this.config.agentDir && this.config.agentName) {
|
|
2138
|
+
const agentFile = join(
|
|
2139
|
+
this.config.cwd,
|
|
2140
|
+
".codex",
|
|
2141
|
+
"skaile-agents",
|
|
2142
|
+
`${this.config.agentName}.md`
|
|
2143
|
+
);
|
|
2144
|
+
if (existsSync(agentFile)) {
|
|
2145
|
+
parts.push(readFileSync(agentFile, "utf-8"));
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
if (this.config.systemPrompt) {
|
|
2149
|
+
parts.push(this.config.systemPrompt);
|
|
2150
|
+
}
|
|
2151
|
+
if (parts.length === 0) return void 0;
|
|
2152
|
+
const instrFile = join(this.config.cwd, ".codex", "skaile-session-instructions.md");
|
|
2153
|
+
mkdirSync(dirname(instrFile), { recursive: true });
|
|
2154
|
+
writeFileSync(instrFile, parts.join("\n\n---\n\n"));
|
|
2155
|
+
return instrFile;
|
|
2156
|
+
}
|
|
2157
|
+
handleThreadEvent(event) {
|
|
2158
|
+
switch (event.type) {
|
|
2159
|
+
case "thread.started":
|
|
2160
|
+
this._runtimeSessionId = event.thread_id;
|
|
2161
|
+
return false;
|
|
2162
|
+
case "turn.started":
|
|
2163
|
+
return false;
|
|
2164
|
+
case "item.started":
|
|
2165
|
+
case "item.updated":
|
|
2166
|
+
case "item.completed":
|
|
2167
|
+
this.handleThreadItem(event.item, event.type === "item.completed", event.type);
|
|
2168
|
+
return false;
|
|
2169
|
+
case "turn.completed": {
|
|
2170
|
+
this.flushToolResults();
|
|
2171
|
+
const usage = event.usage;
|
|
2172
|
+
const tokens = usage ? {
|
|
2173
|
+
inputTokens: usage.input_tokens,
|
|
2174
|
+
outputTokens: usage.output_tokens,
|
|
2175
|
+
cacheReadTokens: usage.cached_input_tokens
|
|
2176
|
+
} : null;
|
|
2177
|
+
this._lastTokens = tokens;
|
|
2178
|
+
this.emit("agent-event", {
|
|
2179
|
+
type: "result",
|
|
2180
|
+
subtype: "success",
|
|
2181
|
+
summary: this.activeAssistantText || "Task completed",
|
|
2182
|
+
// Codex SDK does not report per-turn cost. `0` signals "not
|
|
2183
|
+
// reported" — pair with `tokens` to disambiguate from "free".
|
|
2184
|
+
costUsd: 0,
|
|
2185
|
+
...tokens ? { tokens } : {}
|
|
2186
|
+
});
|
|
2187
|
+
this.emit("agent-event", { type: "agent_end" });
|
|
2188
|
+
return true;
|
|
2189
|
+
}
|
|
2190
|
+
case "turn.failed": {
|
|
2191
|
+
this.flushToolResults();
|
|
2192
|
+
const detail = this.classifyErrorMessage(event.error.message);
|
|
2193
|
+
this.emit("agent-event", {
|
|
2194
|
+
type: "error",
|
|
2195
|
+
error: detail.message,
|
|
2196
|
+
detail
|
|
2197
|
+
});
|
|
2198
|
+
this.emit("agent-event", { type: "agent_end" });
|
|
2199
|
+
return true;
|
|
2200
|
+
}
|
|
2201
|
+
case "error":
|
|
2202
|
+
this.log.warn("codex stream error", { message: event.message });
|
|
2203
|
+
this.lastStreamError = event.message;
|
|
2204
|
+
return false;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
handleThreadItem(item, completed, phase) {
|
|
2208
|
+
switch (item.type) {
|
|
2209
|
+
case "agent_message":
|
|
2210
|
+
this.handleAgentMessage(item, completed);
|
|
2211
|
+
break;
|
|
2212
|
+
case "command_execution":
|
|
2213
|
+
this.handleCommandExecution(item, completed, phase);
|
|
2214
|
+
break;
|
|
2215
|
+
case "mcp_tool_call":
|
|
2216
|
+
this.handleMcpToolCall(item, completed, phase);
|
|
2217
|
+
break;
|
|
2218
|
+
case "file_change":
|
|
2219
|
+
this.handleFileChange(item, completed, phase);
|
|
2220
|
+
break;
|
|
2221
|
+
case "web_search":
|
|
2222
|
+
this.handleWebSearch(item, completed, phase);
|
|
2223
|
+
break;
|
|
2224
|
+
case "error":
|
|
2225
|
+
if (completed) this.log.warn("codex reported", { message: item.message });
|
|
2226
|
+
break;
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
handleAgentMessage(item, completed) {
|
|
2230
|
+
if (this.activeAssistantItemId !== item.id) {
|
|
2231
|
+
if (this.activeAssistantItemId) {
|
|
2232
|
+
this.emit("agent-event", {
|
|
2233
|
+
type: "message_end",
|
|
2234
|
+
message: this.buildAssistantMessage(this.activeAssistantText)
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
this.activeAssistantItemId = item.id;
|
|
2238
|
+
this.activeAssistantText = "";
|
|
2239
|
+
this.emit("agent-event", {
|
|
2240
|
+
type: "message_start",
|
|
2241
|
+
message: this.buildAssistantMessage("")
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
const nextText = item.text ?? "";
|
|
2245
|
+
const delta = nextText.startsWith(this.activeAssistantText) ? nextText.slice(this.activeAssistantText.length) : nextText;
|
|
2246
|
+
this.activeAssistantText = nextText;
|
|
2247
|
+
if (delta) {
|
|
2248
|
+
this.emit("agent-event", {
|
|
2249
|
+
type: "message_update",
|
|
2250
|
+
message: this.buildAssistantMessage(nextText),
|
|
2251
|
+
_textDelta: delta
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
if (completed) {
|
|
2255
|
+
this.emit("agent-event", {
|
|
2256
|
+
type: "message_end",
|
|
2257
|
+
message: this.buildAssistantMessage(nextText)
|
|
2258
|
+
});
|
|
2259
|
+
this.activeAssistantItemId = null;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
handleCommandExecution(item, completed, phase) {
|
|
2263
|
+
if (phase === "item.started") {
|
|
2264
|
+
this.emit("agent-event", {
|
|
2265
|
+
type: "tool_call",
|
|
2266
|
+
name: "Bash",
|
|
2267
|
+
tool: { name: "Bash" },
|
|
2268
|
+
input: { command: item.command }
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
if (completed) {
|
|
2272
|
+
this.pendingToolResults.push({
|
|
2273
|
+
role: "tool",
|
|
2274
|
+
toolName: "Bash",
|
|
2275
|
+
content: "",
|
|
2276
|
+
data: {
|
|
2277
|
+
stdout: item.aggregated_output,
|
|
2278
|
+
stderr: "",
|
|
2279
|
+
exitCode: item.exit_code,
|
|
2280
|
+
status: item.status,
|
|
2281
|
+
command: item.command
|
|
2282
|
+
},
|
|
2283
|
+
isError: item.status === "failed"
|
|
2284
|
+
});
|
|
2285
|
+
this.emit("agent-event", {
|
|
2286
|
+
type: "tool_execution_end",
|
|
2287
|
+
toolName: "Bash"
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
handleMcpToolCall(item, completed, phase) {
|
|
2292
|
+
const toolName = `mcp__${item.server}__${item.tool}`;
|
|
2293
|
+
if (phase === "item.started") {
|
|
2294
|
+
this.emit("agent-event", {
|
|
2295
|
+
type: "tool_call",
|
|
2296
|
+
name: toolName,
|
|
2297
|
+
tool: { name: toolName },
|
|
2298
|
+
input: this.toObject(item.arguments)
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
2301
|
+
if (completed) {
|
|
2302
|
+
this.pendingToolResults.push({
|
|
2303
|
+
role: "tool",
|
|
2304
|
+
toolName,
|
|
2305
|
+
content: "",
|
|
2306
|
+
data: {
|
|
2307
|
+
status: item.status,
|
|
2308
|
+
content: item.result?.content,
|
|
2309
|
+
structuredContent: item.result?.structured_content,
|
|
2310
|
+
error: item.error?.message
|
|
2311
|
+
},
|
|
2312
|
+
isError: item.status === "failed"
|
|
2313
|
+
});
|
|
2314
|
+
this.emit("agent-event", {
|
|
2315
|
+
type: "tool_execution_end",
|
|
2316
|
+
toolName
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
handleFileChange(item, completed, phase) {
|
|
2321
|
+
if (phase === "item.started") {
|
|
2322
|
+
this.emit("agent-event", {
|
|
2323
|
+
type: "tool_call",
|
|
2324
|
+
name: "ApplyPatch",
|
|
2325
|
+
tool: { name: "ApplyPatch" },
|
|
2326
|
+
input: { changes: item.changes }
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
if (completed) {
|
|
2330
|
+
this.pendingToolResults.push({
|
|
2331
|
+
role: "tool",
|
|
2332
|
+
toolName: "ApplyPatch",
|
|
2333
|
+
content: "",
|
|
2334
|
+
data: { status: item.status, changes: item.changes },
|
|
2335
|
+
isError: item.status === "failed"
|
|
2336
|
+
});
|
|
2337
|
+
this.emit("agent-event", {
|
|
2338
|
+
type: "tool_execution_end",
|
|
2339
|
+
toolName: "ApplyPatch"
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
handleWebSearch(item, completed, phase) {
|
|
2344
|
+
if (phase === "item.started") {
|
|
2345
|
+
this.emit("agent-event", {
|
|
2346
|
+
type: "tool_call",
|
|
2347
|
+
name: "WebSearch",
|
|
2348
|
+
tool: { name: "WebSearch" },
|
|
2349
|
+
input: { query: item.query }
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
if (completed) {
|
|
2353
|
+
this.pendingToolResults.push({
|
|
2354
|
+
role: "tool",
|
|
2355
|
+
toolName: "WebSearch",
|
|
2356
|
+
content: "",
|
|
2357
|
+
data: { query: item.query, status: "completed" }
|
|
2358
|
+
});
|
|
2359
|
+
this.emit("agent-event", {
|
|
2360
|
+
type: "tool_execution_end",
|
|
2361
|
+
toolName: "WebSearch"
|
|
2362
|
+
});
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
flushToolResults() {
|
|
2366
|
+
if (this.pendingToolResults.length === 0) return;
|
|
2367
|
+
this.emit("agent-event", {
|
|
2368
|
+
type: "turn_end",
|
|
2369
|
+
toolResults: [...this.pendingToolResults]
|
|
2370
|
+
});
|
|
2371
|
+
this.pendingToolResults = [];
|
|
2372
|
+
}
|
|
2373
|
+
buildAssistantMessage(text2) {
|
|
2374
|
+
return {
|
|
2375
|
+
role: "assistant",
|
|
2376
|
+
content: [{ type: "text", text: text2 }]
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
toObject(value) {
|
|
2380
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
2381
|
+
return value;
|
|
2382
|
+
}
|
|
2383
|
+
return { arguments: value };
|
|
2384
|
+
}
|
|
2385
|
+
classifyErrorMessage(rawMessage) {
|
|
2386
|
+
const friendly = friendlyCodexError(rawMessage, { chatgptLogin: this.chatgptLogin });
|
|
2387
|
+
if (friendly) {
|
|
2388
|
+
return {
|
|
2389
|
+
message: friendly.message,
|
|
2390
|
+
category: friendly.category,
|
|
2391
|
+
retryable: false,
|
|
2392
|
+
hint: friendly.hint
|
|
2393
|
+
};
|
|
2394
|
+
}
|
|
2395
|
+
const message = rawMessage;
|
|
2396
|
+
const generic = classifyGenericErrorMessage(message);
|
|
2397
|
+
if (generic) {
|
|
2398
|
+
const hint = generic.category === "auth" ? "Check your OpenAI API key configuration in settings or environment." : generic.category === "rate_limit" ? "Codex hit an OpenAI rate limit. Wait briefly and try again." : generic.category === "model" ? "The selected Codex model is unavailable. Choose a different model." : "A network error occurred while talking to Codex.";
|
|
2399
|
+
return { message, category: generic.category, retryable: generic.retryable, hint };
|
|
2400
|
+
}
|
|
2401
|
+
const lower = message.toLowerCase();
|
|
2402
|
+
if (lower.includes("sandbox") || lower.includes("approval") || lower.includes("permission") || lower.includes("git repo")) {
|
|
2403
|
+
return {
|
|
2404
|
+
message,
|
|
2405
|
+
category: "config",
|
|
2406
|
+
retryable: false,
|
|
2407
|
+
hint: "Check Codex sandbox, approval, and workspace configuration."
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2410
|
+
return {
|
|
2411
|
+
message,
|
|
2412
|
+
category: "unknown",
|
|
2413
|
+
retryable: true,
|
|
2414
|
+
hint: "An unexpected Codex error occurred. Try the prompt again."
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
};
|
|
2418
|
+
function isManagedCodexConfig(config) {
|
|
2419
|
+
const codex = config.harnessOptions?.codex;
|
|
2420
|
+
return Boolean(
|
|
2421
|
+
codex?.managedRuntime || codex?.authSource !== void 0 && codex.authSource !== "local"
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
var codexHarnessTarget = {
|
|
2425
|
+
id: "codex",
|
|
2426
|
+
displayName: BUILTIN_HARNESS_CATALOG.codex?.name ?? "OpenAI Codex",
|
|
2427
|
+
apiVersion: 1,
|
|
2428
|
+
// Harnesses take AgentConfig; passthrough — full schema validation is out of scope.
|
|
2429
|
+
configSchema: z.custom(() => true),
|
|
2430
|
+
create: async (config, _ctx) => isManagedCodexConfig(config) ? new ManagedCodexHarness(config) : new CodexHarness(config),
|
|
2431
|
+
listModels: async (registry, options) => {
|
|
2432
|
+
const config = {
|
|
2433
|
+
cwd: process.cwd(),
|
|
2434
|
+
sessionId: "model-list",
|
|
2435
|
+
...registryHarnessConfig(registry, options)
|
|
2436
|
+
};
|
|
2437
|
+
return (isManagedCodexConfig(config) ? new ManagedCodexHarness(config) : new CodexHarness(config)).listModels();
|
|
2438
|
+
}
|
|
2439
|
+
};
|
|
2440
|
+
|
|
2441
|
+
export { CodexHarness, buildCodexConfigDocument, checkCodexCredential, codexHarnessTarget, codexInstanceEnvironment, codexLoginFallbackNotice, isManagedCodexConfig, parseCodexCredential, parseCodexRefreshInput, parseCodexRefreshResult, prepareNativePolicy };
|
|
2442
|
+
//# sourceMappingURL=chunk-HLFLET7S.js.map
|
|
2443
|
+
//# sourceMappingURL=chunk-HLFLET7S.js.map
|