agent-tower 0.7.4-beta.1 → 0.7.4-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/executors/__tests__/codex-mcp-windows.test.d.ts +2 -0
- package/dist/executors/__tests__/codex-mcp-windows.test.d.ts.map +1 -0
- package/dist/executors/__tests__/codex-mcp-windows.test.js +128 -0
- package/dist/executors/__tests__/codex-mcp-windows.test.js.map +1 -0
- package/dist/executors/__tests__/codex.executor.test.js +394 -1
- package/dist/executors/__tests__/codex.executor.test.js.map +1 -1
- package/dist/executors/codex.executor.d.ts +6 -6
- package/dist/executors/codex.executor.d.ts.map +1 -1
- package/dist/executors/codex.executor.js +118 -32
- package/dist/executors/codex.executor.js.map +1 -1
- package/dist/mcp/tools/tasks.js +1 -1
- package/dist/mcp/tools/tasks.js.map +1 -1
- package/dist/mcp/types.js +1 -1
- package/dist/mcp/types.js.map +1 -1
- package/dist/runtime/__tests__/acp-process-manager.test.js +65 -0
- package/dist/runtime/__tests__/acp-process-manager.test.js.map +1 -1
- package/dist/runtime/acp/agents/pi-coding-agent.d.ts.map +1 -1
- package/dist/runtime/acp/agents/pi-coding-agent.js +3 -0
- package/dist/runtime/acp/agents/pi-coding-agent.js.map +1 -1
- package/dist/services/__tests__/task.service.test.js +69 -7
- package/dist/services/__tests__/task.service.test.js.map +1 -1
- package/dist/services/__tests__/workspace.service.test.js +31 -0
- package/dist/services/__tests__/workspace.service.test.js.map +1 -1
- package/dist/services/task-display-state.d.ts +10 -0
- package/dist/services/task-display-state.d.ts.map +1 -0
- package/dist/services/task-display-state.js +45 -0
- package/dist/services/task-display-state.js.map +1 -0
- package/dist/services/task.service.d.ts +14 -6
- package/dist/services/task.service.d.ts.map +1 -1
- package/dist/services/task.service.js +70 -83
- package/dist/services/task.service.js.map +1 -1
- package/dist/services/workspace.service.d.ts.map +1 -1
- package/dist/services/workspace.service.js +2 -1
- package/dist/services/workspace.service.js.map +1 -1
- package/dist/web/assets/{AgentDemoPage-DiHEh1iv.js → AgentDemoPage-CrkoVCOZ.js} +1 -1
- package/dist/web/assets/{AgentEnvironmentSettingsPage-B_Xk7Bef.js → AgentEnvironmentSettingsPage-BRihy3_Q.js} +1 -1
- package/dist/web/assets/{AttachmentPreview-BngGSahV.js → AttachmentPreview-B9z1Mxb-.js} +1 -1
- package/dist/web/assets/{BranchSelect-BgVKUnxj.js → BranchSelect-Bo5uhKCt.js} +1 -1
- package/dist/web/assets/{BrandLogo-CBahjTd7.js → BrandLogo-DEL42B23.js} +1 -1
- package/dist/web/assets/{ConversationPage-kgJIlyS5.js → ConversationPage-C46RuN9U.js} +1 -1
- package/dist/web/assets/{CreateTaskInput-Cllw4abW.js → CreateTaskInput-4aHJpsfL.js} +1 -1
- package/dist/web/assets/{DemoPage-CkS__9CE.js → DemoPage-CAZqNag-.js} +1 -1
- package/dist/web/assets/{DesignSystemPage-BklN99GA.js → DesignSystemPage-CjCYC24B.js} +1 -1
- package/dist/web/assets/{ExecutionPermissionSelector-Bsi1aa7q.js → ExecutionPermissionSelector-CJgc_EVo.js} +1 -1
- package/dist/web/assets/{GeneralSettingsPage-BC9TUWiu.js → GeneralSettingsPage-DFT7Cubi.js} +1 -1
- package/dist/web/assets/{LoadingPreviewPage-Dx8Si6-g.js → LoadingPreviewPage-CB4VDPAZ.js} +1 -1
- package/dist/web/assets/{McpSettingsPage-fqXRGtFu.js → McpSettingsPage-CgiyGvkH.js} +1 -1
- package/dist/web/assets/{MemberAvatar-CZ3F53Ep.js → MemberAvatar-Cfz8LU6k.js} +1 -1
- package/dist/web/assets/MobileTaskDetail-yZNyZMQt.js +3 -0
- package/dist/web/assets/{NotificationSettingsPage-D9KGIFVA.js → NotificationSettingsPage-BSMJ45yx.js} +1 -1
- package/dist/web/assets/{ProfileSettingsPage-D_Hw5lpd.js → ProfileSettingsPage-BwNThOnH.js} +1 -1
- package/dist/web/assets/ProjectKanbanPage-DtZW0bOh.js +14 -0
- package/dist/web/assets/{ProjectSettingsPage-CJANCPJX.js → ProjectSettingsPage-CMBMkbgI.js} +1 -1
- package/dist/web/assets/{ProviderSettingsPage-B6Ro5PQa.js → ProviderSettingsPage-D759BSRa.js} +1 -1
- package/dist/web/assets/{SettingsMasterDetail-Q-049Dvh.js → SettingsMasterDetail-DpYUTM-Q.js} +1 -1
- package/dist/web/assets/TaskDetail-rRHGOvSw.js +1 -0
- package/dist/web/assets/{TaskHandoffPanel-BpGiZ5eT.js → TaskHandoffPanel-DBcC1xQW.js} +24 -24
- package/dist/web/assets/{TeamSettingsPage-DbKgqEO_.js → TeamSettingsPage-BszQfKp0.js} +1 -1
- package/dist/web/assets/{TranslationSettingsPage-Ba-iypOV.js → TranslationSettingsPage-lLaxWCy3.js} +1 -1
- package/dist/web/assets/{arc-SOqs3Svk.js → arc-DtgXFw4D.js} +1 -1
- package/dist/web/assets/{architectureDiagram-3BPJPVTR-BH25ukq_.js → architectureDiagram-3BPJPVTR-B3p3EMX1.js} +1 -1
- package/dist/web/assets/{at-sign-CEsum1vJ.js → at-sign-B8rXgKEE.js} +1 -1
- package/dist/web/assets/{blockDiagram-GPEHLZMM-CKA17iNP.js → blockDiagram-GPEHLZMM-BmekCfr0.js} +1 -1
- package/dist/web/assets/{c4Diagram-AAUBKEIU-DnZ_8oOf.js → c4Diagram-AAUBKEIU-CF6hJhuL.js} +1 -1
- package/dist/web/assets/channel-BdK_fAel.js +1 -0
- package/dist/web/assets/check-DwK55QS2.js +1 -0
- package/dist/web/assets/{chevron-down-Bmwy-nvS.js → chevron-down-Dftc5hr4.js} +1 -1
- package/dist/web/assets/{chevron-up-CClL6-SF.js → chevron-up-CKRxN6zX.js} +1 -1
- package/dist/web/assets/{chip-BSMT2uT1.js → chip-ZTBHcXU2.js} +1 -1
- package/dist/web/assets/{chunk-2J33WTMH-BrtUyBDF.js → chunk-2J33WTMH-Bjelop4M.js} +1 -1
- package/dist/web/assets/{chunk-4BX2VUAB-DZDNetDC.js → chunk-4BX2VUAB-B1rJPbL6.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-xVa3bJqv.js → chunk-55IACEB6-B5qfH7Gj.js} +1 -1
- package/dist/web/assets/{chunk-727SXJPM-B7n9vxY4.js → chunk-727SXJPM-ClFMbgrH.js} +1 -1
- package/dist/web/assets/{chunk-AQP2D5EJ-DQd4G76y.js → chunk-AQP2D5EJ-BMFjTO7B.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-CXyP08kl.js → chunk-FMBD7UC4-Pr1oOFfR.js} +1 -1
- package/dist/web/assets/{chunk-ND2GUHAM-BtnIS-HK.js → chunk-ND2GUHAM-BPm4TRpO.js} +1 -1
- package/dist/web/assets/{chunk-QZHKN3VN-D4D-V_ex.js → chunk-QZHKN3VN-DCPA_U0M.js} +1 -1
- package/dist/web/assets/classDiagram-4FO5ZUOK-CFzCq0ml.js +1 -0
- package/dist/web/assets/classDiagram-v2-Q7XG4LA2-CFzCq0ml.js +1 -0
- package/dist/web/assets/{code-block-OCS4YCEC-DfERo9L1.js → code-block-OCS4YCEC-Cx53F4d7.js} +1 -1
- package/dist/web/assets/{confirm-dialog-8ItnHBi0.js → confirm-dialog-C_j-w4TS.js} +1 -1
- package/dist/web/assets/{content-DrW8KnXu.js → content-DeIfej_n.js} +1 -1
- package/dist/web/assets/{copy-prrQrInw.js → copy-B-BaLIlh.js} +1 -1
- package/dist/web/assets/{cose-bilkent-S5V4N54A-CXdH8Idq.js → cose-bilkent-S5V4N54A-gIys-0yu.js} +1 -1
- package/dist/web/assets/{cssMode-CJ6_f10q.js → cssMode-xF0bjbbU.js} +1 -1
- package/dist/web/assets/{dagre-BM42HDAG-XZydDwXe.js → dagre-BM42HDAG-Bwc3Ntc-.js} +1 -1
- package/dist/web/assets/{diagram-2AECGRRQ-CofEe0Fr.js → diagram-2AECGRRQ-zkydBsMT.js} +1 -1
- package/dist/web/assets/{diagram-5GNKFQAL-BbTtW5WH.js → diagram-5GNKFQAL-p9As3QtI.js} +1 -1
- package/dist/web/assets/{diagram-KO2AKTUF-BJHZXpUM.js → diagram-KO2AKTUF-N_pUU-iU.js} +1 -1
- package/dist/web/assets/{diagram-LMA3HP47-DN7A27YU.js → diagram-LMA3HP47-CA2rl4Gq.js} +1 -1
- package/dist/web/assets/{diagram-OG6HWLK6-4IdYadiw.js → diagram-OG6HWLK6-BoXxBlUC.js} +1 -1
- package/dist/web/assets/{dialog-BUsI4h2U.js → dialog-CnjWIdWp.js} +1 -1
- package/dist/web/assets/{erDiagram-TEJ5UH35-BFijhCWv.js → erDiagram-TEJ5UH35-CVBHQTCN.js} +1 -1
- package/dist/web/assets/{file-C6QMUr5_.js → file-jMLGlXos.js} +1 -1
- package/dist/web/assets/{flowDiagram-I6XJVG4X-D9MxC-El.js → flowDiagram-I6XJVG4X-kK7iKiAv.js} +1 -1
- package/dist/web/assets/{folder-picker-CBdw-4v3.js → folder-picker-CR8LiSQf.js} +1 -1
- package/dist/web/assets/{freemarker2-CJVobC22.js → freemarker2-CTMcTiBd.js} +1 -1
- package/dist/web/assets/{ganttDiagram-6RSMTGT7-BijCAyPQ.js → ganttDiagram-6RSMTGT7-CJVvLTkW.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-PVQCEYII-AvI5QqxQ.js → gitGraphDiagram-PVQCEYII-D8uj_SCw.js} +1 -1
- package/dist/web/assets/{globe-BOfcTsvL.js → globe-FFVKce9-.js} +1 -1
- package/dist/web/assets/{handlebars-DTqWmI25.js → handlebars-NHxbmOs3.js} +1 -1
- package/dist/web/assets/{html-Bx7L8DX_.js → html-oFqzz32q.js} +1 -1
- package/dist/web/assets/{htmlMode-D_S0NjZc.js → htmlMode-sgeetciK.js} +1 -1
- package/dist/web/assets/index-408ws6FY.css +1 -0
- package/dist/web/assets/index-9KNEuwGB.js +122 -0
- package/dist/web/assets/{index-Bg8TlDo_.js → index-BzqyZD7L.js} +1 -1
- package/dist/web/assets/{index-BFcDmhr0.js → index-C5lNOKJb.js} +1 -1
- package/dist/web/assets/{index-BClyhWqz.js → index-C9vIrQ3Q.js} +1 -1
- package/dist/web/assets/{index-Cdmj8aPv.js → index-Cm_7q6ju.js} +1 -1
- package/dist/web/assets/{index-DUmVscrR.js → index-CwfWGzxi.js} +4 -4
- package/dist/web/assets/{index-COigMgrJ.js → index-D8TNxwWk.js} +1 -1
- package/dist/web/assets/{index-JXcQeorP.js → index-biTPUXAe.js} +1 -1
- package/dist/web/assets/{index-CtOu4p9B.js → index-gqrRO6ZJ.js} +1 -1
- package/dist/web/assets/{infoDiagram-5YYISTIA-76JLqjJ0.js → infoDiagram-5YYISTIA-CExRZE3Y.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-YF4QCWOH-CXHFTVly.js → ishikawaDiagram-YF4QCWOH-CsqM2X33.js} +1 -1
- package/dist/web/assets/javascript-COt-61wn.js +1 -0
- package/dist/web/assets/{journeyDiagram-JHISSGLW-6SDJs9C5.js → journeyDiagram-JHISSGLW-DX9OMhPD.js} +1 -1
- package/dist/web/assets/{jsonMode-BGg9MhFR.js → jsonMode-VQg5cpLV.js} +1 -1
- package/dist/web/assets/{kanban-definition-UN3LZRKU-C0Y3zy1P.js → kanban-definition-UN3LZRKU-CWHMx6vc.js} +1 -1
- package/dist/web/assets/{kbd-D4KIW7lm.js → kbd-GGofKfKu.js} +1 -1
- package/dist/web/assets/{key-round-C4vhwqUu.js → key-round-p-Fpykfj.js} +1 -1
- package/dist/web/assets/{layers-BBHgMnhN.js → layers-pbPa_eZq.js} +1 -1
- package/dist/web/assets/{linear-CjeIgBwO.js → linear-9VwGhaEA.js} +1 -1
- package/dist/web/assets/{link-C-Jq7dnc.js → link-CG4UwIS1.js} +1 -1
- package/dist/web/assets/{liquid-guocpCJB.js → liquid-SJInUaiB.js} +1 -1
- package/dist/web/assets/{lspLanguageFeatures-CPXERxVW.js → lspLanguageFeatures-DJOdVyBc.js} +1 -1
- package/dist/web/assets/{mdx-DA4ymd3l.js → mdx-CToYr-3b.js} +1 -1
- package/dist/web/assets/{menu-BgtmXAzT.js → menu-BFQ1mKU7.js} +1 -1
- package/dist/web/assets/{mermaid-NOHMQCX5-XQbfQDGS.js → mermaid-NOHMQCX5-BOViobLk.js} +58 -58
- package/dist/web/assets/{message-square-DIFv9yrO.js → message-square-BYZsUsLz.js} +1 -1
- package/dist/web/assets/{mindmap-definition-RKZ34NQL-6ucD61UV.js → mindmap-definition-RKZ34NQL-BTcDnn6X.js} +1 -1
- package/dist/web/assets/{modal-Dx4N46s5.js → modal-BT_7Lhtw.js} +1 -1
- package/dist/web/assets/{monaco-Mn1xgio_.js → monaco-DUftz48_.js} +3 -3
- package/dist/web/assets/{nav-row-DwEkbw3N.js → nav-row-BjDckaPC.js} +1 -1
- package/dist/web/assets/{paperclip-CdQvl5ee.js → paperclip-CFAH5neE.js} +1 -1
- package/dist/web/assets/{pencil-ChX7G9j9.js → pencil-Bg9TWm_-.js} +1 -1
- package/dist/web/assets/{people-D_VwidfV.js → people-BIQwajM2.js} +1 -1
- package/dist/web/assets/{pieDiagram-4H26LBE5-KVvAyrIn.js → pieDiagram-4H26LBE5-CRjqdnP8.js} +1 -1
- package/dist/web/assets/{popover-CGEFZKuu.js → popover-DpYhrrCE.js} +1 -1
- package/dist/web/assets/{python-BzNNRn8W.js → python-DbmUucgH.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-W4KKPZXB-ClVzvpE7.js → quadrantDiagram-W4KKPZXB-uvA8TYC6.js} +1 -1
- package/dist/web/assets/{razor-DF2ZxqCE.js → razor-B0AJhw7W.js} +1 -1
- package/dist/web/assets/{requirementDiagram-4Y6WPE33-uXN6Uoln.js → requirementDiagram-4Y6WPE33-BWLugaKX.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-5OEKKPKP-Bi3Tc0wR.js → sankeyDiagram-5OEKKPKP-DHrbZZv8.js} +1 -1
- package/dist/web/assets/{search--FRFbCre.js → search-BH8nze-X.js} +1 -1
- package/dist/web/assets/{select-c5IIpQj0.js → select-BegY9RkP.js} +1 -1
- package/dist/web/assets/{select-D0sHgfcU.js → select-EcEr5QSS.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-3UESZ5HK-CLdrO2PY.js → sequenceDiagram-3UESZ5HK-CKwEKcTn.js} +1 -1
- package/dist/web/assets/{server-Dr_6KJFM.js → server-BxEUjx0l.js} +1 -1
- package/dist/web/assets/{stateDiagram-AJRCARHV-BGInldGc.js → stateDiagram-AJRCARHV-vhgmSpa9.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-BHNVJYJU-C0z5OTY4.js +1 -0
- package/dist/web/assets/{textarea-W-xQPto3.js → textarea-CvoGGROs.js} +1 -1
- package/dist/web/assets/{timeline-definition-PNZ67QCA-BUp6Qykc.js → timeline-definition-PNZ67QCA-bSDKcwDZ.js} +1 -1
- package/dist/web/assets/{toggles-Bgp7vdwg.js → toggles-CEDUD08u.js} +1 -1
- package/dist/web/assets/{tooltip-DZcaSN1F.js → tooltip-CKaUbVZ7.js} +1 -1
- package/dist/web/assets/{track-Cj1Ogapu.js → track-1qw2cvTy.js} +1 -1
- package/dist/web/assets/{trash-2-DmFXzIhh.js → trash-2-ChiXon87.js} +1 -1
- package/dist/web/assets/{tsMode-CSHMQttX.js → tsMode-bnV8W2Ru.js} +1 -1
- package/dist/web/assets/{typescript-CyQKmnN-.js → typescript-d1MHIeEx.js} +1 -1
- package/dist/web/assets/{upload-C-4QDrwN.js → upload-CQ-cQaOz.js} +1 -1
- package/dist/web/assets/{use-profiles-D5mIgeQU.js → use-profiles-CJzoqarX.js} +1 -1
- package/dist/web/assets/{use-providers-BViTes-6.js → use-providers-S29TCag2.js} +1 -1
- package/dist/web/assets/{use-translation-settings-Dgw0oKP1.js → use-translation-settings-DifNJaCu.js} +1 -1
- package/dist/web/assets/{useNormalizedLogs-BX7pMg0-.js → useNormalizedLogs-BVdY8qDk.js} +1 -1
- package/dist/web/assets/{useTokenUsage-T03faPfn.js → useTokenUsage-nh_KDA3q.js} +1 -1
- package/dist/web/assets/{vennDiagram-CIIHVFJN-iQYnP9Ux.js → vennDiagram-CIIHVFJN-D7QbDYzC.js} +1 -1
- package/dist/web/assets/{wardley-L42UT6IY-J7vXwA5J.js → wardley-L42UT6IY-C9aHlNVa.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-YWT4CUSO-CdtM5GJv.js → wardleyDiagram-YWT4CUSO-CZhubDRC.js} +1 -1
- package/dist/web/assets/{xml-C79KdvcD.js → xml-DAKKxHAr.js} +1 -1
- package/dist/web/assets/{xychartDiagram-2RQKCTM6-B3gcBChQ.js → xychartDiagram-2RQKCTM6-BgDlpFBh.js} +1 -1
- package/dist/web/assets/{yaml-C1WLpuVe.js → yaml-D_hSVVz0.js} +1 -1
- package/dist/web/index.html +2 -2
- package/node_modules/@agent-tower/shared/dist/types.d.ts +12 -2
- package/node_modules/@agent-tower/shared/dist/types.d.ts.map +1 -1
- package/node_modules/@agent-tower/shared/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/assets/MobileTaskDetail-B68iYPOI.js +0 -3
- package/dist/web/assets/ProjectKanbanPage-CUX0Kh-t.js +0 -12
- package/dist/web/assets/TaskDetail-DBtWf0YV.js +0 -1
- package/dist/web/assets/channel-xU45IQlP.js +0 -1
- package/dist/web/assets/check-DUA2DjGH.js +0 -1
- package/dist/web/assets/classDiagram-4FO5ZUOK-pvRulc6s.js +0 -1
- package/dist/web/assets/classDiagram-v2-Q7XG4LA2-pvRulc6s.js +0 -1
- package/dist/web/assets/index-BDON669P.css +0 -1
- package/dist/web/assets/index-BRu1Yhrx.js +0 -122
- package/dist/web/assets/javascript-80FDzX3y.js +0 -1
- package/dist/web/assets/stateDiagram-v2-BHNVJYJU-D44x8SXb.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-mcp-windows.test.d.ts","sourceRoot":"","sources":["../../../src/executors/__tests__/codex-mcp-windows.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { buildAgentTowerMcpConfigOverrides, getCodexMcpServerNamesForLaunch, resetCodexMcpServerCache, } from '../codex.executor.js';
|
|
6
|
+
import { ExecutionEnv } from '../execution-env.js';
|
|
7
|
+
const mocks = vi.hoisted(() => ({ execFile: vi.fn() }));
|
|
8
|
+
vi.mock('child_process', async (importOriginal) => ({
|
|
9
|
+
...await importOriginal(),
|
|
10
|
+
execFile: mocks.execFile,
|
|
11
|
+
}));
|
|
12
|
+
const nativePlatform = process.platform;
|
|
13
|
+
const platformDescriptor = Object.getOwnPropertyDescriptor(process, 'platform');
|
|
14
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-mcp-windows-'));
|
|
15
|
+
const workingDir = path.join(tmpDir, 'project with spaces');
|
|
16
|
+
const codexHome = path.join(tmpDir, 'codex-home');
|
|
17
|
+
fs.mkdirSync(path.join(workingDir, '.codex'), { recursive: true });
|
|
18
|
+
fs.mkdirSync(codexHome);
|
|
19
|
+
fs.writeFileSync(path.join(workingDir, '.codex', 'config.toml'), '[mcp_servers.agent-tower]\ncommand="old-tower"');
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
Object.defineProperty(process, 'platform', { ...platformDescriptor, value: 'win32' });
|
|
22
|
+
resetCodexMcpServerCache();
|
|
23
|
+
mocks.execFile.mockReset();
|
|
24
|
+
mocks.execFile.mockImplementation((_command, _args, _options, callback) => {
|
|
25
|
+
callback(null, JSON.stringify([{ name: 'agent-tower' }, { name: 'project-only' }]));
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
Object.defineProperty(process, 'platform', platformDescriptor);
|
|
30
|
+
resetCodexMcpServerCache();
|
|
31
|
+
});
|
|
32
|
+
afterAll(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); });
|
|
33
|
+
function environment(overrides = {}) {
|
|
34
|
+
return { CODEX_HOME: codexHome, PROVIDER_SENTINEL: 'provider-value', ...overrides };
|
|
35
|
+
}
|
|
36
|
+
describe('Windows Codex MCP launch discovery', () => {
|
|
37
|
+
it.each(['cmd', 'BAT'])('loads project MCPs through a .%s shim with cwd, env and config arguments', async (extension) => {
|
|
38
|
+
const executable = path.join(tmpDir, `npm directory/codex.${extension}`);
|
|
39
|
+
const env = environment({ ComSpec: 'custom-cmd.exe' });
|
|
40
|
+
const overrides = ['--profile', 'work profile', '-c', 'model="test model"'];
|
|
41
|
+
const names = await getCodexMcpServerNamesForLaunch(overrides, undefined, { program: executable, args: overrides }, env, workingDir);
|
|
42
|
+
expect(names).toEqual(new Set(['agent-tower', 'project-only']));
|
|
43
|
+
expect(mocks.execFile).toHaveBeenCalledOnce();
|
|
44
|
+
const [command, args, options] = mocks.execFile.mock.calls[0];
|
|
45
|
+
expect(command).toBe('custom-cmd.exe');
|
|
46
|
+
expect(args.slice(0, 3)).toEqual(['/d', '/s', '/c']);
|
|
47
|
+
expect(args[3]).toContain('codex.' + extension);
|
|
48
|
+
expect(args[3]).toContain('--profile "work profile"');
|
|
49
|
+
expect(args[3]).toContain('-c "model=\\"test model\\""');
|
|
50
|
+
expect(args[3]).toMatch(/ mcp list --json"$/);
|
|
51
|
+
expect(options).toMatchObject({
|
|
52
|
+
cwd: fs.realpathSync(workingDir), env, timeout: 3000, windowsHide: true, windowsVerbatimArguments: true,
|
|
53
|
+
});
|
|
54
|
+
expect(options.shell).not.toBe(true);
|
|
55
|
+
const sessionEnv = ExecutionEnv.default(workingDir).merge({
|
|
56
|
+
AGENT_TOWER_AGENT_CREDENTIAL: 'windows-session-credential',
|
|
57
|
+
AGENT_TOWER_URL: 'http://127.0.0.1:42001',
|
|
58
|
+
AGENT_TOWER_SESSION_ID: 'windows-mcp-session',
|
|
59
|
+
});
|
|
60
|
+
expect(buildAgentTowerMcpConfigOverrides(sessionEnv, names)).toContain('mcp_servers.agent-tower.enabled=false');
|
|
61
|
+
});
|
|
62
|
+
it.each(['ComSpec', 'COMSPEC', undefined])('selects the configured command interpreter (%s)', async (key) => {
|
|
63
|
+
const env = environment(key ? { [key]: 'selected-cmd.exe' } : {});
|
|
64
|
+
await getCodexMcpServerNamesForLaunch([], undefined, { program: path.join(tmpDir, 'codex.cmd'), args: [] }, env, workingDir);
|
|
65
|
+
expect(mocks.execFile.mock.calls[0][0]).toBe(key ? 'selected-cmd.exe' : 'cmd.exe');
|
|
66
|
+
});
|
|
67
|
+
it('resolves an npm shim from a Windows Path variable', async () => {
|
|
68
|
+
const binDir = path.join(tmpDir, 'npm-bin');
|
|
69
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
70
|
+
fs.writeFileSync(path.join(binDir, 'codex.CMD'), '');
|
|
71
|
+
const env = environment({ Path: binDir, PATHEXT: '.EXE;.CMD;.BAT' });
|
|
72
|
+
await getCodexMcpServerNamesForLaunch([], undefined, { program: 'codex', args: [] }, env, workingDir);
|
|
73
|
+
expect(mocks.execFile.mock.calls[0][0]).toBe('cmd.exe');
|
|
74
|
+
expect(mocks.execFile.mock.calls[0][1][3]).toContain('codex.CMD');
|
|
75
|
+
});
|
|
76
|
+
it('retains a relative custom shim and resolves it from the target workspace', async () => {
|
|
77
|
+
await getCodexMcpServerNamesForLaunch([], undefined, { program: './tools/custom-codex.cmd', args: ['--profile', 'custom'] }, environment(), workingDir);
|
|
78
|
+
const args = mocks.execFile.mock.calls[0][1];
|
|
79
|
+
expect(args[3]).toContain('custom-codex.cmd');
|
|
80
|
+
// cmd quoting escapes Windows path separators; normalize only for this assertion.
|
|
81
|
+
expect(args[3].replace(/\\\\/g, '\\')).toContain(path.join(workingDir, 'tools', 'custom-codex.cmd'));
|
|
82
|
+
expect(args[3]).toContain('--profile custom mcp list --json');
|
|
83
|
+
});
|
|
84
|
+
it('keeps native executables and missing custom commands on the direct path', async () => {
|
|
85
|
+
for (const program of [path.join(tmpDir, 'native codex.exe'), 'custom-missing-codex']) {
|
|
86
|
+
const args = ['-c', 'model="test model"'];
|
|
87
|
+
await getCodexMcpServerNamesForLaunch(args, undefined, { program, args }, environment(), workingDir);
|
|
88
|
+
expect(mocks.execFile).toHaveBeenLastCalledWith(program, [...args, 'mcp', 'list', '--json'], expect.not.objectContaining({ windowsVerbatimArguments: true }), expect.any(Function));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
it('retains the existing global fallback if the command interpreter fails', async () => {
|
|
92
|
+
const configPath = path.join(tmpDir, 'fallback.toml');
|
|
93
|
+
fs.writeFileSync(configPath, '[mcp_servers.global-only]\ncommand="fallback-mcp"');
|
|
94
|
+
mocks.execFile.mockImplementation((_command, _args, _options, callback) => callback(new Error('interpreter failed'), ''));
|
|
95
|
+
const names = await getCodexMcpServerNamesForLaunch([], configPath, { program: path.join(tmpDir, 'codex.cmd'), args: [] }, environment(), workingDir);
|
|
96
|
+
expect(names).toEqual(new Set(['global-only']));
|
|
97
|
+
});
|
|
98
|
+
it('does not wrap a .cmd filename on Unix', async () => {
|
|
99
|
+
Object.defineProperty(process, 'platform', { ...platformDescriptor, value: 'linux' });
|
|
100
|
+
const executable = path.join(tmpDir, 'unix-codex.cmd');
|
|
101
|
+
await getCodexMcpServerNamesForLaunch([], undefined, { program: executable, args: [] }, environment(), workingDir);
|
|
102
|
+
expect(mocks.execFile).toHaveBeenCalledWith(executable, ['mcp', 'list', '--json'], expect.not.objectContaining({ windowsVerbatimArguments: true }), expect.any(Function));
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe.skipIf(nativePlatform !== 'win32')('native Windows npm shim discovery', () => {
|
|
106
|
+
it.each(['cmd', 'bat'])('round-trips project configuration and arguments through a real .%s shim', async (extension) => {
|
|
107
|
+
const nativeChildProcess = await vi.importActual('child_process');
|
|
108
|
+
mocks.execFile.mockImplementation(nativeChildProcess.execFile);
|
|
109
|
+
const script = path.join(tmpDir, 'native-probe.cjs');
|
|
110
|
+
const captured = path.join(tmpDir, `native-args-${extension}.json`);
|
|
111
|
+
fs.writeFileSync(script, [
|
|
112
|
+
'const fs = require("node:fs");',
|
|
113
|
+
`fs.writeFileSync(${JSON.stringify(captured)}, JSON.stringify({args: process.argv.slice(2), cwd: process.cwd(), sentinel: process.env.PROVIDER_SENTINEL}));`,
|
|
114
|
+
'const config = fs.readFileSync(".codex/config.toml", "utf8");',
|
|
115
|
+
'process.stdout.write(JSON.stringify(config.includes("mcp_servers.agent-tower") ? [{name:"agent-tower"}] : []));',
|
|
116
|
+
].join('\n'));
|
|
117
|
+
const shim = path.join(tmpDir, `native codex.${extension}`);
|
|
118
|
+
fs.writeFileSync(shim, `@echo off\r\n"${process.execPath}" "${script}" %*\r\n`);
|
|
119
|
+
const args = ['-c', 'model="test model"'];
|
|
120
|
+
const env = environment({ ...process.env, CODEX_HOME: codexHome });
|
|
121
|
+
const names = await getCodexMcpServerNamesForLaunch(args, undefined, { program: shim, args }, env, workingDir);
|
|
122
|
+
expect(names).toEqual(new Set(['agent-tower']));
|
|
123
|
+
expect(JSON.parse(fs.readFileSync(captured, 'utf8'))).toEqual({
|
|
124
|
+
args: [...args, 'mcp', 'list', '--json'], cwd: fs.realpathSync(workingDir), sentinel: 'provider-value',
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
//# sourceMappingURL=codex-mcp-windows.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-mcp-windows.test.js","sourceRoot":"","sources":["../../../src/executors/__tests__/codex-mcp-windows.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,8 @@ import { describe, expect, it, afterAll, afterEach, beforeEach, vi } from 'vites
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
-
import {
|
|
5
|
+
import { parse as parseToml } from 'smol-toml';
|
|
6
|
+
import { buildAgentTowerMcpConfigOverrides, buildAgentTowerMcpEnvConfigOverrides, getCodexDeclaredMcpServerNames, queryCodexMcpServerNames, detectDeclaredMcpServers, getCachedCodexMcpServerNames, getCodexMcpServerNamesForLaunch, resetCodexMcpServerCache, waitForCodexMcpServerCacheRefresh, CODEX_HTTP_ONLY_OPENAI_PROVIDER_ID, getCodexPermissionMode, CodexExecutor, } from '../codex.executor.js';
|
|
6
7
|
import { AGENT_SUBPROCESS_BLOCKED_ENV_KEYS, AGENT_TOWER_MCP_IDENTITY_ENV_KEYS, AGENT_TOWER_MCP_SERVICE_ENV_KEYS, ExecutionEnv, } from '../execution-env.js';
|
|
7
8
|
import { getExecutorByProvider, createExecutorFromProvider } from '../index.js';
|
|
8
9
|
import { createProvider as createRawProvider, reloadProviders, updateProvider } from '../providers.js';
|
|
@@ -637,6 +638,7 @@ function createAsyncProbeCodex(name, stdout, options = {}) {
|
|
|
637
638
|
const script = [
|
|
638
639
|
'#!/bin/sh',
|
|
639
640
|
`printf '1\\n' >> "${countFile}"`,
|
|
641
|
+
`pwd >> "${path.join(binDir, 'probe-cwds')}"`,
|
|
640
642
|
`sleep ${delaySeconds}`,
|
|
641
643
|
'cat <<\'FAKE_EOF\'',
|
|
642
644
|
stdout,
|
|
@@ -653,6 +655,34 @@ function probeCount(binDir) {
|
|
|
653
655
|
return 0;
|
|
654
656
|
return fs.readFileSync(file, 'utf-8').trim().split('\n').filter(Boolean).length;
|
|
655
657
|
}
|
|
658
|
+
function createWorkspaceProbeCodex(name, options = {}) {
|
|
659
|
+
const binDir = path.join(tmpDir, `workspace-probe-${name}`);
|
|
660
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
661
|
+
const script = `#!${process.execPath}
|
|
662
|
+
const fs = require('node:fs');
|
|
663
|
+
const path = require('node:path');
|
|
664
|
+
fs.appendFileSync(${JSON.stringify(path.join(binDir, 'probe-count'))}, '1\\n');
|
|
665
|
+
fs.appendFileSync(${JSON.stringify(path.join(binDir, 'probe-cwds'))}, process.cwd() + '\\n');
|
|
666
|
+
setTimeout(() => {
|
|
667
|
+
const files = [path.join(process.env.CODEX_HOME, 'config.toml')];
|
|
668
|
+
if (!${Boolean(options.ignoreProject)}) {
|
|
669
|
+
for (let dir = process.cwd(); ; dir = path.dirname(dir)) {
|
|
670
|
+
files.push(path.join(dir, '.codex', 'config.toml'));
|
|
671
|
+
if (path.dirname(dir) === dir) break;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const names = new Set();
|
|
675
|
+
for (const file of files) {
|
|
676
|
+
try {
|
|
677
|
+
for (const match of fs.readFileSync(file, 'utf8').matchAll(/^\\[mcp_servers\\.([a-zA-Z0-9_-]+)\\]/gm)) names.add(match[1]);
|
|
678
|
+
} catch {}
|
|
679
|
+
}
|
|
680
|
+
process.stdout.write(JSON.stringify([...names].map(name => ({ name }))));
|
|
681
|
+
}, ${options.delayMs ?? 0});
|
|
682
|
+
`;
|
|
683
|
+
fs.writeFileSync(path.join(binDir, 'codex'), script, { mode: 0o755 });
|
|
684
|
+
return binDir;
|
|
685
|
+
}
|
|
656
686
|
describe('getCachedCodexMcpServerNames', () => {
|
|
657
687
|
it('returns fallback names immediately and probes without blocking the event loop', async () => {
|
|
658
688
|
const binDir = createAsyncProbeCodex('nonblocking', JSON.stringify([{ name: 'agent-tower' }]), { delayMs: 500 });
|
|
@@ -724,6 +754,126 @@ describe('getCachedCodexMcpServerNames', () => {
|
|
|
724
754
|
await waitForCodexMcpServerCacheRefresh();
|
|
725
755
|
expect(probeCount(binDir)).toBe(1);
|
|
726
756
|
});
|
|
757
|
+
it('awaits effective workspace and ancestor declarations on the first launch without blocking the event loop', async () => {
|
|
758
|
+
const binDir = createWorkspaceProbeCodex('workspace-fallback', { delayMs: 100 });
|
|
759
|
+
const home = path.join(tmpDir, 'workspace-fallback-home');
|
|
760
|
+
const project = path.join(tmpDir, 'workspace-fallback-project');
|
|
761
|
+
const workingDir = path.join(project, 'packages', 'app');
|
|
762
|
+
fs.mkdirSync(home, { recursive: true });
|
|
763
|
+
fs.mkdirSync(path.join(project, '.codex'), { recursive: true });
|
|
764
|
+
fs.mkdirSync(path.join(workingDir, '.codex'), { recursive: true });
|
|
765
|
+
fs.writeFileSync(path.join(home, 'config.toml'), '[mcp_servers.global-tool]\ncommand="global-mcp"');
|
|
766
|
+
fs.writeFileSync(path.join(project, '.codex', 'config.toml'), '[mcp_servers.agent-tower]\nurl="https://old.example/mcp"');
|
|
767
|
+
const sessionEnv = ExecutionEnv.default(workingDir).merge({
|
|
768
|
+
AGENT_TOWER_AGENT_CREDENTIAL: 'project-session-credential',
|
|
769
|
+
AGENT_TOWER_URL: 'http://127.0.0.1:42001',
|
|
770
|
+
AGENT_TOWER_SESSION_ID: 'project-fallback-session',
|
|
771
|
+
});
|
|
772
|
+
const generatedName = buildAgentTowerMcpConfigOverrides(sessionEnv, new Set())
|
|
773
|
+
.find(arg => /^mcp_servers\.agent-tower-[a-f0-9]{8}=/.test(arg))
|
|
774
|
+
.split('=')[0].slice('mcp_servers.'.length);
|
|
775
|
+
fs.writeFileSync(path.join(workingDir, '.codex', 'config.toml'), [
|
|
776
|
+
'[mcp_servers.agent-tower-dev]', 'command="old-dev-mcp"',
|
|
777
|
+
`[mcp_servers.${generatedName}]`, 'url="https://old-name.example/mcp"',
|
|
778
|
+
].join('\n'));
|
|
779
|
+
const environment = { ...process.env, PATH: `${binDir}:${savedPath}`, CODEX_HOME: home };
|
|
780
|
+
const probe = { program: 'codex', args: [] };
|
|
781
|
+
const expected = new Set(['global-tool', 'agent-tower', 'agent-tower-dev', generatedName]);
|
|
782
|
+
let eventLoopAdvanced = false;
|
|
783
|
+
const tick = new Promise(resolve => setTimeout(() => { eventLoopAdvanced = true; resolve(); }, 0));
|
|
784
|
+
const initial = await getCodexMcpServerNamesForLaunch([], undefined, probe, environment, workingDir);
|
|
785
|
+
expect(eventLoopAdvanced).toBe(true);
|
|
786
|
+
await tick;
|
|
787
|
+
expect(initial).toEqual(expected);
|
|
788
|
+
const overrides = buildAgentTowerMcpConfigOverrides(sessionEnv, initial);
|
|
789
|
+
expect(overrides).toContain('mcp_servers.agent-tower.enabled=false');
|
|
790
|
+
expect(overrides).toContain('mcp_servers.agent-tower-dev.enabled=false');
|
|
791
|
+
expect(overrides.some(arg => arg.startsWith(`mcp_servers.${generatedName}-2=`))).toBe(true);
|
|
792
|
+
await waitForCodexMcpServerCacheRefresh();
|
|
793
|
+
expect(getCachedCodexMcpServerNames([], undefined, probe, environment, workingDir)).toEqual(expected);
|
|
794
|
+
expect(fs.readFileSync(path.join(binDir, 'probe-cwds'), 'utf8').trim()).toBe(fs.realpathSync(workingDir));
|
|
795
|
+
});
|
|
796
|
+
it('isolates cached probes for workspaces sharing the same executable and CODEX_HOME', async () => {
|
|
797
|
+
const binDir = createWorkspaceProbeCodex('workspace-isolation');
|
|
798
|
+
const home = path.join(tmpDir, 'workspace-isolation-home');
|
|
799
|
+
fs.mkdirSync(home, { recursive: true });
|
|
800
|
+
const environment = { ...process.env, PATH: `${binDir}:${savedPath}`, CODEX_HOME: home };
|
|
801
|
+
const probe = { program: 'codex', args: [] };
|
|
802
|
+
const workspaces = ['first', 'second'].map(name => {
|
|
803
|
+
const workspace = path.join(tmpDir, `isolated-${name}`);
|
|
804
|
+
fs.mkdirSync(path.join(workspace, '.codex'), { recursive: true });
|
|
805
|
+
fs.writeFileSync(path.join(workspace, '.codex', 'config.toml'), `[mcp_servers.${name}-tool]\ncommand="${name}-mcp"`);
|
|
806
|
+
return workspace;
|
|
807
|
+
});
|
|
808
|
+
const [first, second] = await Promise.all(workspaces.map(workspace => getCodexMcpServerNamesForLaunch([], undefined, probe, environment, workspace)));
|
|
809
|
+
expect(first).toEqual(new Set(['first-tool']));
|
|
810
|
+
expect(second).toEqual(new Set(['second-tool']));
|
|
811
|
+
expect(probeCount(binDir)).toBe(2);
|
|
812
|
+
expect(new Set(fs.readFileSync(path.join(binDir, 'probe-cwds'), 'utf8').trim().split('\n'))).toEqual(new Set(workspaces.map(workspace => fs.realpathSync(workspace))));
|
|
813
|
+
expect(getCachedCodexMcpServerNames([], undefined, probe, environment, workspaces[0])).toEqual(new Set(['first-tool']));
|
|
814
|
+
expect(getCachedCodexMcpServerNames([], undefined, probe, environment, workspaces[1])).toEqual(new Set(['second-tool']));
|
|
815
|
+
});
|
|
816
|
+
it.each(['workspace', 'ancestor'])('invalidates the cache when a %s config is created, changed or removed', async (scope) => {
|
|
817
|
+
const binDir = createWorkspaceProbeCodex(`workspace-config-change-${scope}`);
|
|
818
|
+
const home = path.join(tmpDir, `project-config-change-home-${scope}`);
|
|
819
|
+
const project = path.join(tmpDir, `project-config-change-${scope}`);
|
|
820
|
+
const workingDir = path.join(project, 'nested', 'app');
|
|
821
|
+
fs.mkdirSync(home, { recursive: true });
|
|
822
|
+
fs.mkdirSync(workingDir, { recursive: true });
|
|
823
|
+
const environment = { ...process.env, PATH: `${binDir}:${savedPath}`, CODEX_HOME: home };
|
|
824
|
+
const probe = { program: 'codex', args: [] };
|
|
825
|
+
const configPath = path.join(scope === 'workspace' ? workingDir : project, '.codex', 'config.toml');
|
|
826
|
+
const lookup = () => getCodexMcpServerNamesForLaunch([], undefined, probe, environment, workingDir);
|
|
827
|
+
expect(await lookup()).toEqual(new Set());
|
|
828
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
829
|
+
fs.writeFileSync(configPath, '[mcp_servers.agent-tower]\ncommand="old-mcp"');
|
|
830
|
+
expect(await lookup()).toEqual(new Set(['agent-tower']));
|
|
831
|
+
fs.writeFileSync(configPath, '[mcp_servers.agent-tower-dev]\ncommand="another-mcp"');
|
|
832
|
+
expect(await lookup()).toEqual(new Set(['agent-tower-dev']));
|
|
833
|
+
fs.unlinkSync(configPath);
|
|
834
|
+
expect(await lookup()).toEqual(new Set());
|
|
835
|
+
expect(probeCount(binDir)).toBeGreaterThanOrEqual(3);
|
|
836
|
+
});
|
|
837
|
+
it('does not fabricate disabled aliases from a project ignored by the CLI trust rules', async () => {
|
|
838
|
+
const binDir = createWorkspaceProbeCodex('untrusted-project', { ignoreProject: true });
|
|
839
|
+
const home = path.join(tmpDir, 'untrusted-project-home');
|
|
840
|
+
const workingDir = path.join(tmpDir, 'untrusted-project-workspace');
|
|
841
|
+
fs.mkdirSync(home, { recursive: true });
|
|
842
|
+
fs.mkdirSync(path.join(workingDir, '.codex'), { recursive: true });
|
|
843
|
+
fs.writeFileSync(path.join(workingDir, '.codex', 'config.toml'), '[mcp_servers.agent-tower]\ncommand="ignored-project-mcp"');
|
|
844
|
+
const environment = { ...process.env, PATH: `${binDir}:${savedPath}`, CODEX_HOME: home };
|
|
845
|
+
const names = await getCodexMcpServerNamesForLaunch([], undefined, { program: 'codex', args: [] }, environment, workingDir);
|
|
846
|
+
const sessionEnv = ExecutionEnv.default(workingDir).merge({
|
|
847
|
+
AGENT_TOWER_AGENT_CREDENTIAL: 'untrusted-session-credential',
|
|
848
|
+
AGENT_TOWER_URL: 'http://127.0.0.1:42001',
|
|
849
|
+
AGENT_TOWER_SESSION_ID: 'untrusted-project-session',
|
|
850
|
+
});
|
|
851
|
+
expect(names).toEqual(new Set());
|
|
852
|
+
const overrides = buildAgentTowerMcpConfigOverrides(sessionEnv, names);
|
|
853
|
+
expect(overrides).not.toContain('mcp_servers.agent-tower.enabled=false');
|
|
854
|
+
expect(managedMcpServer(overrides).enabled).toBe(true);
|
|
855
|
+
});
|
|
856
|
+
it('waits for its shared refresh when a launch cache expires', async () => {
|
|
857
|
+
const name = 'launch-expired-cache';
|
|
858
|
+
const binDir = createAsyncProbeCodex(name, '[{"name":"previous-tool"}]');
|
|
859
|
+
const home = path.join(tmpDir, 'launch-expired-home');
|
|
860
|
+
fs.mkdirSync(home, { recursive: true });
|
|
861
|
+
const environment = { ...process.env, PATH: `${binDir}:${savedPath}`, CODEX_HOME: home };
|
|
862
|
+
const probe = { program: 'codex', args: [] };
|
|
863
|
+
const date = vi.spyOn(Date, 'now').mockReturnValue(1_000_000);
|
|
864
|
+
try {
|
|
865
|
+
expect(await getCodexMcpServerNamesForLaunch([], undefined, probe, environment, tmpDir)).toEqual(new Set(['previous-tool']));
|
|
866
|
+
createAsyncProbeCodex(name, '[{"name":"current-tool"}]', { delayMs: 50 });
|
|
867
|
+
date.mockReturnValue(1_030_001);
|
|
868
|
+
const results = await Promise.all(Array.from({ length: 3 }, () => getCodexMcpServerNamesForLaunch([], undefined, probe, environment, tmpDir)));
|
|
869
|
+
expect(results).toEqual(Array.from({ length: 3 }, () => new Set(['current-tool'])));
|
|
870
|
+
expect(probeCount(binDir)).toBe(2);
|
|
871
|
+
}
|
|
872
|
+
finally {
|
|
873
|
+
await waitForCodexMcpServerCacheRefresh();
|
|
874
|
+
date.mockRestore();
|
|
875
|
+
}
|
|
876
|
+
});
|
|
727
877
|
});
|
|
728
878
|
// ─── getCodexDeclaredMcpServerNames (config.toml fallback) ───────
|
|
729
879
|
describe('getCodexDeclaredMcpServerNames', () => {
|
|
@@ -1107,4 +1257,247 @@ describe('buildAgentTowerMcpEnvConfigOverrides', () => {
|
|
|
1107
1257
|
]);
|
|
1108
1258
|
});
|
|
1109
1259
|
});
|
|
1260
|
+
function managedMcpServer(args) {
|
|
1261
|
+
const assignment = args.find(arg => /^mcp_servers\.agent-tower-[a-f0-9]{8}(?:-\d+)?=/.test(arg));
|
|
1262
|
+
expect(assignment).toBeDefined();
|
|
1263
|
+
const parsed = parseToml(assignment);
|
|
1264
|
+
const name = Object.keys(parsed.mcp_servers)[0];
|
|
1265
|
+
const server = parsed.mcp_servers[name];
|
|
1266
|
+
server.env = {};
|
|
1267
|
+
for (const arg of args.filter(arg => arg.startsWith(`mcp_servers.${name}.env.`))) {
|
|
1268
|
+
const projected = parseToml(arg);
|
|
1269
|
+
Object.assign(server.env, projected.mcp_servers[name].env);
|
|
1270
|
+
}
|
|
1271
|
+
return server;
|
|
1272
|
+
}
|
|
1273
|
+
describe('managed Codex CLI MCP registration', () => {
|
|
1274
|
+
beforeEach(() => {
|
|
1275
|
+
for (const key of [
|
|
1276
|
+
...AGENT_TOWER_MCP_IDENTITY_ENV_KEYS, ...AGENT_TOWER_MCP_SERVICE_ENV_KEYS,
|
|
1277
|
+
'AGENT_TOWER_MCP_ENTRY', 'AGENT_TOWER_DESKTOP_RUNTIME_MODE', 'AGENT_TOWER_NODE_RUNTIME',
|
|
1278
|
+
'AGENT_TOWER_DESKTOP_NODE', 'ELECTRON_RUN_AS_NODE',
|
|
1279
|
+
])
|
|
1280
|
+
vi.stubEnv(key, undefined);
|
|
1281
|
+
});
|
|
1282
|
+
afterEach(() => { vi.unstubAllEnvs(); });
|
|
1283
|
+
it('registers the source MCP without user configuration and scopes its environment to the current session', () => {
|
|
1284
|
+
vi.stubEnv('AGENT_TOWER_AGENT_CREDENTIAL', 'parent-agent-secret');
|
|
1285
|
+
vi.stubEnv('AGENT_TOWER_INTERNAL_TOKEN', 'parent-internal-secret');
|
|
1286
|
+
vi.stubEnv('AGENT_TOWER_SESSION_ID', 'parent-session');
|
|
1287
|
+
vi.stubEnv('AGENT_TOWER_TEAM_RUN_ID', 'parent-team');
|
|
1288
|
+
const env = ExecutionEnv.default(tmpDir).merge({
|
|
1289
|
+
AGENT_TOWER_AGENT_CREDENTIAL: 'current-agent-credential',
|
|
1290
|
+
AGENT_TOWER_INTERNAL_TOKEN: 'must-not-forward-internal',
|
|
1291
|
+
AGENT_TOWER_URL: 'http://127.0.0.1:42001',
|
|
1292
|
+
AGENT_TOWER_PORT: '42001',
|
|
1293
|
+
AGENT_TOWER_SESSION_ID: 'current-session',
|
|
1294
|
+
AGENT_TOWER_INVOCATION_ID: 'current-invocation',
|
|
1295
|
+
AGENT_TOWER_TEAM_RUN_ID: 'current-team',
|
|
1296
|
+
AGENT_TOWER_MEMBER_ID: 'current-member',
|
|
1297
|
+
OPENAI_API_KEY: 'provider-secret',
|
|
1298
|
+
});
|
|
1299
|
+
const args = buildAgentTowerMcpConfigOverrides(env, new Set());
|
|
1300
|
+
const server = managedMcpServer(args);
|
|
1301
|
+
expect(server).toEqual({
|
|
1302
|
+
command: process.execPath,
|
|
1303
|
+
args: ['--import', expect.stringMatching(/[\\/]tsx[\\/]dist[\\/]loader\.mjs$/), path.resolve('packages/server/src/mcp/index.ts')],
|
|
1304
|
+
enabled: true,
|
|
1305
|
+
env: {
|
|
1306
|
+
AGENT_TOWER_URL: 'http://127.0.0.1:42001',
|
|
1307
|
+
AGENT_TOWER_PORT: '42001',
|
|
1308
|
+
AGENT_TOWER_AGENT_CREDENTIAL: 'current-agent-credential',
|
|
1309
|
+
AGENT_TOWER_SESSION_ID: 'current-session',
|
|
1310
|
+
AGENT_TOWER_INVOCATION_ID: 'current-invocation',
|
|
1311
|
+
AGENT_TOWER_TEAM_RUN_ID: 'current-team',
|
|
1312
|
+
AGENT_TOWER_MEMBER_ID: 'current-member',
|
|
1313
|
+
},
|
|
1314
|
+
});
|
|
1315
|
+
expect(args.join(' ')).not.toMatch(/parent-|must-not-forward-internal|provider-secret/);
|
|
1316
|
+
const registration = args.find(arg => /^mcp_servers\.agent-tower-[a-f0-9]{8}=/.test(arg));
|
|
1317
|
+
expect(registration).not.toContain('current-agent-credential');
|
|
1318
|
+
expect(registration).not.toContain('env');
|
|
1319
|
+
expect(args.some(arg => arg.startsWith('mcp_servers.agent-tower.env.'))).toBe(false);
|
|
1320
|
+
});
|
|
1321
|
+
it('uses a distinct backend and credential for each session without inheriting a previous team identity', () => {
|
|
1322
|
+
vi.stubEnv('AGENT_TOWER_TEAM_RUN_ID', 'stale-parent-team');
|
|
1323
|
+
const first = ExecutionEnv.default(tmpDir).merge({
|
|
1324
|
+
AGENT_TOWER_AGENT_CREDENTIAL: 'first-credential', AGENT_TOWER_PORT: '42001',
|
|
1325
|
+
AGENT_TOWER_SESSION_ID: 'first-session', AGENT_TOWER_TEAM_RUN_ID: 'first-team',
|
|
1326
|
+
});
|
|
1327
|
+
const second = ExecutionEnv.default(tmpDir).merge({
|
|
1328
|
+
AGENT_TOWER_AGENT_CREDENTIAL: 'second-credential', AGENT_TOWER_URL: 'http://127.0.0.1:42002',
|
|
1329
|
+
AGENT_TOWER_SESSION_ID: 'second-session',
|
|
1330
|
+
});
|
|
1331
|
+
const firstServer = managedMcpServer(buildAgentTowerMcpConfigOverrides(first, new Set()));
|
|
1332
|
+
const secondServer = managedMcpServer(buildAgentTowerMcpConfigOverrides(second, new Set()));
|
|
1333
|
+
expect(firstServer.env.AGENT_TOWER_URL).toBe('http://127.0.0.1:42001');
|
|
1334
|
+
expect(firstServer.env.AGENT_TOWER_TEAM_RUN_ID).toBe('first-team');
|
|
1335
|
+
expect(secondServer.env).toEqual({
|
|
1336
|
+
AGENT_TOWER_URL: 'http://127.0.0.1:42002',
|
|
1337
|
+
AGENT_TOWER_AGENT_CREDENTIAL: 'second-credential',
|
|
1338
|
+
AGENT_TOWER_SESSION_ID: 'second-session',
|
|
1339
|
+
});
|
|
1340
|
+
});
|
|
1341
|
+
it.each(['workspace', 'packaged'])('uses the trusted %s runtime entry and keeps its Node environment', mode => {
|
|
1342
|
+
const entry = path.join(tmpDir, `${mode} install`, 'dist', 'mcp', 'index.js');
|
|
1343
|
+
fs.mkdirSync(path.dirname(entry), { recursive: true });
|
|
1344
|
+
fs.writeFileSync(entry, '');
|
|
1345
|
+
vi.stubEnv('AGENT_TOWER_MCP_ENTRY', entry);
|
|
1346
|
+
vi.stubEnv('AGENT_TOWER_DESKTOP_RUNTIME_MODE', mode);
|
|
1347
|
+
vi.stubEnv('AGENT_TOWER_NODE_RUNTIME', 'C:\\Program Files\\Agent Tower\\node.exe');
|
|
1348
|
+
vi.stubEnv('ELECTRON_RUN_AS_NODE', '1');
|
|
1349
|
+
const env = ExecutionEnv.default(tmpDir).merge({
|
|
1350
|
+
AGENT_TOWER_URL: 'http://127.0.0.1:12580',
|
|
1351
|
+
AGENT_TOWER_INTERNAL_TOKEN: 'trusted-internal-token',
|
|
1352
|
+
});
|
|
1353
|
+
const server = managedMcpServer(buildAgentTowerMcpConfigOverrides(env, new Set()));
|
|
1354
|
+
expect(server.command).toBe(mode === 'packaged' ? 'C:\\Program Files\\Agent Tower\\node.exe' : process.execPath);
|
|
1355
|
+
expect(server.args).toEqual([entry]);
|
|
1356
|
+
expect(server.env).toEqual({
|
|
1357
|
+
AGENT_TOWER_URL: 'http://127.0.0.1:12580',
|
|
1358
|
+
AGENT_TOWER_INTERNAL_TOKEN: 'trusted-internal-token',
|
|
1359
|
+
...(mode === 'packaged' ? { ELECTRON_RUN_AS_NODE: '1' } : {}),
|
|
1360
|
+
});
|
|
1361
|
+
});
|
|
1362
|
+
it('disables declared Tower endpoints without sharing credentials and leaves unrelated MCP servers alone', () => {
|
|
1363
|
+
const env = ExecutionEnv.default(tmpDir).merge({
|
|
1364
|
+
AGENT_TOWER_URL: 'http://127.0.0.1:42001',
|
|
1365
|
+
AGENT_TOWER_AGENT_CREDENTIAL: 'session-credential',
|
|
1366
|
+
AGENT_TOWER_INTERNAL_TOKEN: 'must-not-forward-internal',
|
|
1367
|
+
AGENT_TOWER_SESSION_ID: 'session',
|
|
1368
|
+
});
|
|
1369
|
+
const args = buildAgentTowerMcpConfigOverrides(env, new Set(['agent-tower', 'agent-tower-dev', 'other-server']));
|
|
1370
|
+
expect(managedMcpServer(args).env.AGENT_TOWER_AGENT_CREDENTIAL).toBe('session-credential');
|
|
1371
|
+
expect(args).toContain('mcp_servers.agent-tower.enabled=false');
|
|
1372
|
+
expect(args).toContain('mcp_servers.agent-tower-dev.enabled=false');
|
|
1373
|
+
expect(args.some(arg => /^mcp_servers\.(agent-tower|agent-tower-dev)\.env\./.test(arg))).toBe(false);
|
|
1374
|
+
expect(args.join(' ')).not.toContain('must-not-forward-internal');
|
|
1375
|
+
expect(args.some(arg => arg.includes('other-server'))).toBe(false);
|
|
1376
|
+
});
|
|
1377
|
+
it('does not register MCP from credentials belonging to a parent process', () => {
|
|
1378
|
+
vi.stubEnv('AGENT_TOWER_AGENT_CREDENTIAL', 'parent-agent-secret');
|
|
1379
|
+
vi.stubEnv('AGENT_TOWER_INTERNAL_TOKEN', 'parent-internal-secret');
|
|
1380
|
+
const env = ExecutionEnv.default(tmpDir).set('AGENT_TOWER_SESSION_ID', 'session');
|
|
1381
|
+
expect(buildAgentTowerMcpConfigOverrides(env, new Set())).toEqual([]);
|
|
1382
|
+
expect(buildAgentTowerMcpConfigOverrides(env, new Set(['agent-tower']))).toEqual([
|
|
1383
|
+
'-c', 'mcp_servers.agent-tower.env.AGENT_TOWER_SESSION_ID="session"',
|
|
1384
|
+
]);
|
|
1385
|
+
});
|
|
1386
|
+
it('chooses an unused managed server name without changing an existing server at the generated name', () => {
|
|
1387
|
+
const env = ExecutionEnv.default(tmpDir).merge({
|
|
1388
|
+
AGENT_TOWER_URL: 'http://127.0.0.1:42001',
|
|
1389
|
+
AGENT_TOWER_AGENT_CREDENTIAL: 'session-credential',
|
|
1390
|
+
AGENT_TOWER_SESSION_ID: 'session-with-existing-server',
|
|
1391
|
+
});
|
|
1392
|
+
const initial = buildAgentTowerMcpConfigOverrides(env, new Set());
|
|
1393
|
+
const assignment = initial.find(arg => /^mcp_servers\.agent-tower-[a-f0-9]{8}=/.test(arg));
|
|
1394
|
+
const existingName = assignment.slice('mcp_servers.'.length, assignment.indexOf('='));
|
|
1395
|
+
const args = buildAgentTowerMcpConfigOverrides(env, new Set([existingName, `${existingName}-2`]));
|
|
1396
|
+
expect(args.some(arg => arg.startsWith(`mcp_servers.${existingName}-3=`))).toBe(true);
|
|
1397
|
+
expect(args.some(arg => arg.startsWith(`mcp_servers.${existingName}.`))).toBe(false);
|
|
1398
|
+
expect(args.some(arg => arg.startsWith(`mcp_servers.${existingName}-2.`))).toBe(false);
|
|
1399
|
+
expect(managedMcpServer(args).env.AGENT_TOWER_AGENT_CREDENTIAL).toBe('session-credential');
|
|
1400
|
+
});
|
|
1401
|
+
it.each(['FULL_ACCESS', 'ASK', 'AUTO_SANDBOX'])('disables project-level Tower aliases on the first new and resumed %s launch', async (executionPermission) => {
|
|
1402
|
+
const home = path.join(tmpDir, `project-launch-home-${executionPermission}`);
|
|
1403
|
+
const workingDir = path.join(tmpDir, `project-launch-workspace-${executionPermission}`);
|
|
1404
|
+
fs.mkdirSync(home, { recursive: true });
|
|
1405
|
+
fs.mkdirSync(path.join(workingDir, '.codex'), { recursive: true });
|
|
1406
|
+
const projectConfig = '[mcp_servers.agent-tower]\nurl="https://old-project.example/mcp"\n[mcp_servers.agent-tower-dev]\ncommand="old-dev-mcp"\n[mcp_servers.project-tool]\ncommand="project-mcp"';
|
|
1407
|
+
const configPath = path.join(workingDir, '.codex', 'config.toml');
|
|
1408
|
+
fs.writeFileSync(configPath, projectConfig);
|
|
1409
|
+
vi.stubEnv('CODEX_HOME', home);
|
|
1410
|
+
const binDir = createWorkspaceProbeCodex(`project-launch-${executionPermission}`, { delayMs: 50 });
|
|
1411
|
+
const executable = path.join(binDir, 'codex');
|
|
1412
|
+
const refresh = vi.spyOn(codexAskCapabilities, 'refreshCodexCliSupport').mockResolvedValue({ supported: true, executable });
|
|
1413
|
+
const support = vi.spyOn(codexAskCapabilities, 'cachedCodexCliSupport').mockReturnValue({ supported: true, executable });
|
|
1414
|
+
const executor = new CodexExecutor({ executionPermission, cmd: { baseCommandOverride: executable } });
|
|
1415
|
+
const stdin = vi.spyOn(executor, 'spawnWithStdin').mockResolvedValue({});
|
|
1416
|
+
const duplex = vi.spyOn(executor, 'spawnWithDuplex').mockResolvedValue({});
|
|
1417
|
+
try {
|
|
1418
|
+
const config = {
|
|
1419
|
+
workingDir, prompt: 'task',
|
|
1420
|
+
env: ExecutionEnv.default(workingDir).merge({
|
|
1421
|
+
AGENT_TOWER_URL: 'http://127.0.0.1:42001',
|
|
1422
|
+
AGENT_TOWER_AGENT_CREDENTIAL: 'project-bound-credential',
|
|
1423
|
+
AGENT_TOWER_SESSION_ID: 'project-launch-session',
|
|
1424
|
+
}),
|
|
1425
|
+
};
|
|
1426
|
+
await executor.spawn(config);
|
|
1427
|
+
await executor.spawnFollowUp(config, 'native-thread');
|
|
1428
|
+
const spawn = executionPermission === 'FULL_ACCESS' ? stdin : duplex;
|
|
1429
|
+
expect(spawn).toHaveBeenCalledTimes(2);
|
|
1430
|
+
for (const [, parts] of spawn.mock.calls) {
|
|
1431
|
+
expect(parts.args).toContain('mcp_servers.agent-tower.enabled=false');
|
|
1432
|
+
expect(parts.args).toContain('mcp_servers.agent-tower-dev.enabled=false');
|
|
1433
|
+
expect(parts.args.some(arg => arg.includes('mcp_servers.project-tool'))).toBe(false);
|
|
1434
|
+
expect(managedMcpServer(parts.args).env.AGENT_TOWER_SESSION_ID).toBe('project-launch-session');
|
|
1435
|
+
}
|
|
1436
|
+
await waitForCodexMcpServerCacheRefresh();
|
|
1437
|
+
expect(fs.readFileSync(path.join(binDir, 'probe-cwds'), 'utf8').trim()).toBe(fs.realpathSync(workingDir));
|
|
1438
|
+
expect(fs.readFileSync(configPath, 'utf8')).toBe(projectConfig);
|
|
1439
|
+
expect(fs.readdirSync(home)).toEqual([]);
|
|
1440
|
+
}
|
|
1441
|
+
finally {
|
|
1442
|
+
await waitForCodexMcpServerCacheRefresh();
|
|
1443
|
+
stdin.mockRestore();
|
|
1444
|
+
duplex.mockRestore();
|
|
1445
|
+
refresh.mockRestore();
|
|
1446
|
+
support.mockRestore();
|
|
1447
|
+
}
|
|
1448
|
+
});
|
|
1449
|
+
it.each(['FULL_ACCESS', 'ASK', 'AUTO_SANDBOX'])('isolates managed MCP from stale user settings on new and resumed %s launches without writing user config', async (executionPermission) => {
|
|
1450
|
+
const home = path.join(tmpDir, `managed-codex-home-${executionPermission}`);
|
|
1451
|
+
fs.mkdirSync(home, { recursive: true });
|
|
1452
|
+
const userConfig = '[mcp_servers.agent-tower]\nurl="https://stale.example/mcp"\nenabled=false\ndisabled_tools=["get_task_handoff"]\n[mcp_servers.other-server]\ncommand="other-mcp"';
|
|
1453
|
+
fs.writeFileSync(path.join(home, 'config.toml'), userConfig);
|
|
1454
|
+
vi.stubEnv('CODEX_HOME', home);
|
|
1455
|
+
const refresh = vi.spyOn(codexAskCapabilities, 'refreshCodexCliSupport').mockResolvedValue({ supported: true, executable: '/verified/codex' });
|
|
1456
|
+
const support = vi.spyOn(codexAskCapabilities, 'cachedCodexCliSupport').mockReturnValue({ supported: true, executable: '/verified/codex' });
|
|
1457
|
+
const executor = new CodexExecutor({
|
|
1458
|
+
executionPermission,
|
|
1459
|
+
settings: userConfig,
|
|
1460
|
+
cmd: { baseCommandOverride: '/verified/codex', env: { AGENT_TOWER_DESKTOP_NODE: '/profile/node' } },
|
|
1461
|
+
});
|
|
1462
|
+
const stdin = vi.spyOn(executor, 'spawnWithStdin').mockResolvedValue({});
|
|
1463
|
+
const duplex = vi.spyOn(executor, 'spawnWithDuplex').mockResolvedValue({});
|
|
1464
|
+
try {
|
|
1465
|
+
const config = {
|
|
1466
|
+
workingDir: tmpDir, prompt: 'task',
|
|
1467
|
+
env: ExecutionEnv.default(tmpDir).merge({
|
|
1468
|
+
AGENT_TOWER_URL: 'http://127.0.0.1:42001',
|
|
1469
|
+
AGENT_TOWER_AGENT_CREDENTIAL: 'bound-session-credential',
|
|
1470
|
+
AGENT_TOWER_SESSION_ID: 'current-session',
|
|
1471
|
+
}),
|
|
1472
|
+
};
|
|
1473
|
+
await executor.spawn(config);
|
|
1474
|
+
await executor.spawnFollowUp(config, 'native-thread');
|
|
1475
|
+
const spawn = executionPermission === 'FULL_ACCESS' ? stdin : duplex;
|
|
1476
|
+
expect(spawn).toHaveBeenCalledTimes(2);
|
|
1477
|
+
const serverNames = new Set(spawn.mock.calls.map(([, parts]) => parts.args.find(arg => /^mcp_servers\.agent-tower-[a-f0-9]{8}(?:-\d+)?=/.test(arg))?.split('=')[0]));
|
|
1478
|
+
expect(serverNames.size).toBe(1);
|
|
1479
|
+
for (const [, parts] of spawn.mock.calls) {
|
|
1480
|
+
const server = managedMcpServer(parts.args);
|
|
1481
|
+
expect(server.command).toBe('/profile/node');
|
|
1482
|
+
expect(server.enabled).toBe(true);
|
|
1483
|
+
expect(server.env.AGENT_TOWER_AGENT_CREDENTIAL).toBe('bound-session-credential');
|
|
1484
|
+
expect(server.env.AGENT_TOWER_SESSION_ID).toBe('current-session');
|
|
1485
|
+
expect(server).not.toHaveProperty('url');
|
|
1486
|
+
expect(server).not.toHaveProperty('disabled_tools');
|
|
1487
|
+
expect(parts.args).toContain('mcp_servers.other-server.command="other-mcp"');
|
|
1488
|
+
expect(parts.args.findIndex(arg => /^mcp_servers\.agent-tower-[a-f0-9]{8}(?:-\d+)?=/.test(arg)))
|
|
1489
|
+
.toBeGreaterThan(parts.args.indexOf('mcp_servers.agent-tower.enabled=false'));
|
|
1490
|
+
}
|
|
1491
|
+
expect(fs.readdirSync(home)).toEqual(['config.toml']);
|
|
1492
|
+
expect(fs.readFileSync(path.join(home, 'config.toml'), 'utf8')).toBe(userConfig);
|
|
1493
|
+
}
|
|
1494
|
+
finally {
|
|
1495
|
+
await waitForCodexMcpServerCacheRefresh();
|
|
1496
|
+
stdin.mockRestore();
|
|
1497
|
+
duplex.mockRestore();
|
|
1498
|
+
refresh.mockRestore();
|
|
1499
|
+
support.mockRestore();
|
|
1500
|
+
}
|
|
1501
|
+
});
|
|
1502
|
+
});
|
|
1110
1503
|
//# sourceMappingURL=codex.executor.test.js.map
|