farming-code 2.2.13 → 2.2.16

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.
Files changed (119) hide show
  1. package/README.md +81 -105
  2. package/README.zh_cn.md +82 -106
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/backend/acp/packaged-codex-acp.js +13 -0
  5. package/backend/acp-runtime.js +192 -14
  6. package/backend/agent-manager.js +176 -510
  7. package/backend/agent-runtime-binding.js +9 -55
  8. package/backend/chat-runtime.js +28 -0
  9. package/backend/codex-transcript.js +2 -1
  10. package/backend/config-manager.js +22 -14
  11. package/backend/control-api.js +3 -1
  12. package/backend/farming-app-cli.js +13 -0
  13. package/backend/farming-session-store.js +2 -1
  14. package/backend/npm-update-helper.js +65 -7
  15. package/backend/provider-adapters.js +6 -2
  16. package/backend/server.js +89 -104
  17. package/backend/storage-layout.js +0 -16
  18. package/backend/update-service.js +94 -5
  19. package/dist/acp/LICENSE.codex-acp +190 -0
  20. package/dist/acp/codex-acp-1.1.4.js +30718 -0
  21. package/dist/assets/App-y6p2yRar.js +206 -0
  22. package/dist/assets/{FileEditorMarkdownPreview-UlVd0O4D.js → FileEditorMarkdownPreview-R0jwwLME.js} +4 -4
  23. package/dist/assets/FileEditorPane-DNy3LAqS.js +2 -0
  24. package/dist/assets/{IconGlyphs-AU22gPDY.js → IconGlyphs-BsUfqpKN.js} +1 -1
  25. package/dist/assets/ProjectFilesSection-BhDEUVHA.js +12 -0
  26. package/dist/assets/{ReviewPage-CeDKDyZh.js → ReviewPage-ChtByBe3.js} +1 -1
  27. package/dist/assets/{abnfDiagram-VRR7QNED-BgawzDQT.js → abnfDiagram-VRR7QNED-nnglrXFi.js} +1 -1
  28. package/dist/assets/{architectureDiagram-ZJ3FMSHR-DUShwPg0.js → architectureDiagram-ZJ3FMSHR-BEXMaFfY.js} +1 -1
  29. package/dist/assets/{blockDiagram-677ZJIJ3-CeGZviXR.js → blockDiagram-677ZJIJ3-1mCezSBk.js} +1 -1
  30. package/dist/assets/{c4Diagram-LMCZKHZV-CLhYht6S.js → c4Diagram-LMCZKHZV-DhTiI6Uv.js} +1 -1
  31. package/dist/assets/channel-BVWNatUB.js +1 -0
  32. package/dist/assets/{chunk-32BRIVSS-LFYpT-Cu.js → chunk-32BRIVSS-CCdsKd73.js} +1 -1
  33. package/dist/assets/{chunk-52WLFC77-BH6jDw9J.js → chunk-52WLFC77-Gncjk4LF.js} +1 -1
  34. package/dist/assets/{chunk-C7G6YPKG-B_mIUlb7.js → chunk-C7G6YPKG-CiDVUP82.js} +1 -1
  35. package/dist/assets/{chunk-EX3LRPZG-C63hVuAJ.js → chunk-EX3LRPZG-z6gXP2_N.js} +1 -1
  36. package/dist/assets/{chunk-FWX5IMBZ-CboX-WF_.js → chunk-FWX5IMBZ-DMZBccQa.js} +2 -2
  37. package/dist/assets/{chunk-HOUHSVGY-BOgSd6Un.js → chunk-HOUHSVGY-D8tIeoPk.js} +1 -1
  38. package/dist/assets/{chunk-ICXQ74PX-C0K-pgZt.js → chunk-ICXQ74PX-b6oQ2Q17.js} +1 -1
  39. package/dist/assets/{chunk-MOJQB5TN-CVMfwN_7.js → chunk-MOJQB5TN-DAVH_p2a.js} +1 -1
  40. package/dist/assets/{chunk-OGEWGWER-DLT3pxyw.js → chunk-OGEWGWER-DxoiMCI-.js} +1 -1
  41. package/dist/assets/{chunk-PUDLZKDR-Cd079yIQ.js → chunk-PUDLZKDR-NsOWh7HM.js} +1 -1
  42. package/dist/assets/{chunk-Q4XR5HBZ-D0jHa6SO.js → chunk-Q4XR5HBZ-Di4H7L21.js} +1 -1
  43. package/dist/assets/{chunk-V7JOEXUC-CVDtR75H.js → chunk-V7JOEXUC-DttpZg9Y.js} +1 -1
  44. package/dist/assets/{chunk-VAUOI2AC-B-W2_9Ot.js → chunk-VAUOI2AC-UHNr_yhX.js} +1 -1
  45. package/dist/assets/{chunk-VR4S4FIN-FD3kWh32.js → chunk-VR4S4FIN-D-Fmtd4Z.js} +1 -1
  46. package/dist/assets/{chunk-WYO6CB5R-C2bGaS5r.js → chunk-WYO6CB5R-CUxMtfK2.js} +4 -4
  47. package/dist/assets/{chunk-ZGVPDNZ5-ymgts0zo.js → chunk-ZGVPDNZ5-CoV2QEl0.js} +1 -1
  48. package/dist/assets/classDiagram-OUVF2IWQ-Db0ib5iE.js +1 -0
  49. package/dist/assets/classDiagram-v2-EOCWNBFH-Bkf69XF9.js +1 -0
  50. package/dist/assets/code-dark-DKBGxoPX.css +1 -0
  51. package/dist/assets/{cynefinDiagram-TSTJHNR4-CwpobXIl.js → cynefinDiagram-TSTJHNR4-0xPI92Lg.js} +1 -1
  52. package/dist/assets/{dagre-VKFMJZFB-Dkhlfjna.js → dagre-VKFMJZFB-DfuMG4gU.js} +1 -1
  53. package/dist/assets/{diagram-FQU43EPY-hA8dicp6.js → diagram-FQU43EPY-CT00bLZ6.js} +1 -1
  54. package/dist/assets/{diagram-G47NLZAW-BTY6JTzO.js → diagram-G47NLZAW-Xbh7zXUx.js} +1 -1
  55. package/dist/assets/{diagram-NH7WQ7WH-xkaHHX9M.js → diagram-NH7WQ7WH-SRDVPfn7.js} +1 -1
  56. package/dist/assets/{diagram-OA4YK3LP-gk_fg9UU.js → diagram-OA4YK3LP-CQRli4Np.js} +1 -1
  57. package/dist/assets/{diagram-WEI45ONY-BvC8xd6T.js → diagram-WEI45ONY-DMf17WQi.js} +1 -1
  58. package/dist/assets/{ebnfDiagram-CCIWWBDH-CR7RaNat.js → ebnfDiagram-CCIWWBDH-DhpFkgJ5.js} +1 -1
  59. package/dist/assets/{erDiagram-Q63AITRT-_M-JFPbj.js → erDiagram-Q63AITRT-1IwxWmJo.js} +1 -1
  60. package/dist/assets/file-icons-DpbrD5ZP.js +1 -0
  61. package/dist/assets/flowDiagram-23GEKE2U-DYFo2xmB.js +1 -0
  62. package/dist/assets/{ganttDiagram-NO4QXBWP-Bl8P1yf_.js → ganttDiagram-NO4QXBWP-BNCdfyr1.js} +1 -1
  63. package/dist/assets/{gitGraphDiagram-IHSO6WYX-DoHa5ysz.js → gitGraphDiagram-IHSO6WYX-pbyWZuiX.js} +1 -1
  64. package/dist/assets/{index-CYZKdoqi.js → index-iKBvPjWS.js} +2 -2
  65. package/dist/assets/{infoDiagram-FWYZ7A6U-DSFcSuUs.js → infoDiagram-FWYZ7A6U-BcTOgDbe.js} +1 -1
  66. package/dist/assets/{ishikawaDiagram-FXEZZL3T-DdkqullI.js → ishikawaDiagram-FXEZZL3T-DCdvGKrn.js} +1 -1
  67. package/dist/assets/{journeyDiagram-5HDEW3XC-B4hRnQJr.js → journeyDiagram-5HDEW3XC-BZ-1q_IT.js} +1 -1
  68. package/dist/assets/{kanban-definition-HUTT4EX6-tm2FZB2E.js → kanban-definition-HUTT4EX6-BIgejCjj.js} +1 -1
  69. package/dist/assets/{line-CmsjK4kn.js → line-CfptFMmX.js} +1 -1
  70. package/dist/assets/main-CdkLOXYl.css +1 -0
  71. package/dist/assets/{mermaid.core-C7UT_EEL.js → mermaid.core-gSpX30pV.js} +3 -3
  72. package/dist/assets/{mindmap-definition-LN4V7U3C-DplfjGms.js → mindmap-definition-LN4V7U3C-BKBaI4LW.js} +1 -1
  73. package/dist/assets/{pegDiagram-2B236MQR-lu3SdkQs.js → pegDiagram-2B236MQR-sBvDY3VX.js} +1 -1
  74. package/dist/assets/{pieDiagram-ENE6RG2P-3fHbmDf9.js → pieDiagram-ENE6RG2P-DWlWcu08.js} +1 -1
  75. package/dist/assets/{quadrantDiagram-ABIIQ3AL-a5qoWz-X.js → quadrantDiagram-ABIIQ3AL-DZutUd3J.js} +1 -1
  76. package/dist/assets/{railroadDiagram-RFXS5EU6-D2TOuXW9.js → railroadDiagram-RFXS5EU6-DQb3lXGM.js} +1 -1
  77. package/dist/assets/{requirementDiagram-TGXJPOKE-LQBWpUvd.js → requirementDiagram-TGXJPOKE-DqFGXu2e.js} +1 -1
  78. package/dist/assets/{sankeyDiagram-HTMAVEWB-B1ohYoyK.js → sankeyDiagram-HTMAVEWB-d7ECahnO.js} +1 -1
  79. package/dist/assets/{sequenceDiagram-DBY2YBRQ-C3vM-hMq.js → sequenceDiagram-DBY2YBRQ-DdsbKqct.js} +1 -1
  80. package/dist/assets/{stateDiagram-2N3HPSRC-BvFhOryI.js → stateDiagram-2N3HPSRC-P6iSrgwR.js} +1 -1
  81. package/dist/assets/stateDiagram-v2-6OUMAXLB-DIYSf_qh.js +1 -0
  82. package/dist/assets/{swimlanes-5IMT3BWC-DKF_b-2s.js → swimlanes-5IMT3BWC-BGnZJx1M.js} +1 -1
  83. package/dist/assets/swimlanesDiagram-G3AALYLV-BIkrlKrZ.js +8 -0
  84. package/dist/assets/{timeline-definition-FHXFAJF6-vAs11ux_.js → timeline-definition-FHXFAJF6-rA-r3aUR.js} +1 -1
  85. package/dist/assets/{vennDiagram-L72KCM5P-BybTX_9B.js → vennDiagram-L72KCM5P-DiVKUjA9.js} +1 -1
  86. package/dist/assets/{wardleyDiagram-EHGQE667-CicUmMBR.js → wardleyDiagram-EHGQE667-Cfxh7UOn.js} +1 -1
  87. package/dist/assets/workspace-editor-model-D0Im34S5.js +1 -0
  88. package/dist/assets/{workspace-editor-monaco-BICJESG0.js → workspace-editor-monaco-BIpqcWf1.js} +1 -1
  89. package/dist/assets/workspace-editor-monaco-DLlws5Ig.js +1 -0
  90. package/dist/assets/{workspace-view-state-DvYG_9PH.js → workspace-view-state-BpPINu84.js} +1 -1
  91. package/dist/assets/{xychartDiagram-FW5EYKEG-BhWDyCX-.js → xychartDiagram-FW5EYKEG--hmryQeL.js} +1 -1
  92. package/dist/crt-mermaid-renderer.js +313 -313
  93. package/dist/index.html +1 -1
  94. package/frontend/session-bridge.js +24 -2
  95. package/frontend/skins/crt/app.js +194 -82
  96. package/frontend/skins/crt/index.html +2 -11
  97. package/frontend/skins/crt/styles/effects.css +2 -34
  98. package/package.json +23 -9
  99. package/shared/browser-protocol.js +3 -3
  100. package/backend/app-server-api.js +0 -917
  101. package/backend/codex-app-server-home.js +0 -39
  102. package/backend/codex-app-server-runtime.js +0 -901
  103. package/dist/assets/App-xASd_Y0F.js +0 -206
  104. package/dist/assets/FileEditorPane-Dxx4rKOg.js +0 -2
  105. package/dist/assets/ProjectFilesSection-DE7Nb9Jk.js +0 -12
  106. package/dist/assets/channel-Uw90AO0b.js +0 -1
  107. package/dist/assets/classDiagram-OUVF2IWQ-DKW_dtku.js +0 -1
  108. package/dist/assets/classDiagram-v2-EOCWNBFH-C8Fi7-yv.js +0 -1
  109. package/dist/assets/code-dark-DPiuyeSp.css +0 -1
  110. package/dist/assets/code-mobile-3C2Rr-CN.css +0 -1
  111. package/dist/assets/file-icons-Bw2qd5iT.js +0 -1
  112. package/dist/assets/flowDiagram-23GEKE2U-DAiS5Y7G.js +0 -1
  113. package/dist/assets/main-Bqtb5kuz.css +0 -1
  114. package/dist/assets/stateDiagram-v2-6OUMAXLB-WMrz4PNo.js +0 -1
  115. package/dist/assets/swimlanesDiagram-G3AALYLV-BGswC9oC.js +0 -8
  116. package/dist/assets/workspace-editor-model-BQol4qbA.js +0 -1
  117. package/dist/assets/workspace-editor-monaco-DRJ7IaXk.js +0 -1
  118. package/scripts/sync-ghostty-vendor.js +0 -66
  119. /package/dist/assets/{core-D0LFJkDt.js → core-B9MwRjDE.js} +0 -0
@@ -1,917 +0,0 @@
1
- const express = require('express');
2
- const crypto = require('crypto');
3
- const path = require('path');
4
- const WebSocket = require('ws');
5
- const packageJson = require('../package.json');
6
-
7
- const DEFAULT_PROVIDER = 'codex';
8
- const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
9
- const MAX_REQUEST_TIMEOUT_MS = 5 * 60_000;
10
- const DEFAULT_INITIALIZE_TIMEOUT_MS = 10_000;
11
- const EVENT_BUFFER_LIMIT = 500;
12
-
13
- const CODEX_APP_SERVER_CLIENT_METHODS = Object.freeze([
14
- 'thread/start',
15
- 'thread/resume',
16
- 'thread/fork',
17
- 'thread/archive',
18
- 'thread/delete',
19
- 'thread/unsubscribe',
20
- 'thread/increment_elicitation',
21
- 'thread/decrement_elicitation',
22
- 'thread/name/set',
23
- 'thread/goal/set',
24
- 'thread/goal/get',
25
- 'thread/goal/clear',
26
- 'thread/metadata/update',
27
- 'thread/settings/update',
28
- 'thread/memoryMode/set',
29
- 'memory/reset',
30
- 'thread/unarchive',
31
- 'thread/compact/start',
32
- 'thread/shellCommand',
33
- 'thread/approveGuardianDeniedAction',
34
- 'thread/backgroundTerminals/clean',
35
- 'thread/backgroundTerminals/list',
36
- 'thread/backgroundTerminals/terminate',
37
- 'thread/rollback',
38
- 'thread/list',
39
- 'thread/search',
40
- 'thread/loaded/list',
41
- 'thread/read',
42
- 'thread/turns/list',
43
- 'thread/items/list',
44
- 'thread/inject_items',
45
- 'skills/list',
46
- 'skills/extraRoots/set',
47
- 'hooks/list',
48
- 'marketplace/add',
49
- 'marketplace/remove',
50
- 'marketplace/upgrade',
51
- 'plugin/list',
52
- 'plugin/installed',
53
- 'plugin/read',
54
- 'plugin/skill/read',
55
- 'plugin/share/save',
56
- 'plugin/share/updateTargets',
57
- 'plugin/share/list',
58
- 'plugin/share/checkout',
59
- 'plugin/share/delete',
60
- 'app/list',
61
- 'fs/readFile',
62
- 'fs/writeFile',
63
- 'fs/createDirectory',
64
- 'fs/getMetadata',
65
- 'fs/readDirectory',
66
- 'fs/remove',
67
- 'fs/copy',
68
- 'fs/watch',
69
- 'fs/unwatch',
70
- 'skills/config/write',
71
- 'plugin/install',
72
- 'plugin/uninstall',
73
- 'turn/start',
74
- 'turn/steer',
75
- 'turn/interrupt',
76
- 'thread/realtime/start',
77
- 'thread/realtime/appendAudio',
78
- 'thread/realtime/appendText',
79
- 'thread/realtime/appendSpeech',
80
- 'thread/realtime/stop',
81
- 'thread/realtime/listVoices',
82
- 'review/start',
83
- 'model/list',
84
- 'modelProvider/capabilities/read',
85
- 'experimentalFeature/list',
86
- 'permissionProfile/list',
87
- 'experimentalFeature/enablement/set',
88
- 'remoteControl/enable',
89
- 'remoteControl/disable',
90
- 'remoteControl/status/read',
91
- 'remoteControl/pairing/start',
92
- 'remoteControl/pairing/status',
93
- 'remoteControl/client/list',
94
- 'remoteControl/client/revoke',
95
- 'collaborationMode/list',
96
- 'environment/add',
97
- 'environment/info',
98
- 'mcpServer/oauth/login',
99
- 'config/mcpServer/reload',
100
- 'mcpServerStatus/list',
101
- 'mcpServer/resource/read',
102
- 'mcpServer/tool/call',
103
- 'windowsSandbox/setupStart',
104
- 'windowsSandbox/readiness',
105
- 'account/login/start',
106
- 'account/login/cancel',
107
- 'account/logout',
108
- 'account/rateLimits/read',
109
- 'account/rateLimitResetCredit/consume',
110
- 'account/usage/read',
111
- 'account/workspaceMessages/read',
112
- 'account/sendAddCreditsNudgeEmail',
113
- 'feedback/upload',
114
- 'command/exec',
115
- 'command/exec/write',
116
- 'command/exec/terminate',
117
- 'command/exec/resize',
118
- 'process/spawn',
119
- 'process/writeStdin',
120
- 'process/kill',
121
- 'process/resizePty',
122
- 'config/read',
123
- 'externalAgentConfig/detect',
124
- 'externalAgentConfig/import',
125
- 'externalAgentConfig/import/readHistories',
126
- 'config/value/write',
127
- 'config/batchWrite',
128
- 'configRequirements/read',
129
- 'account/read',
130
- 'getConversationSummary',
131
- 'gitDiffToRemote',
132
- 'getAuthStatus',
133
- 'fuzzyFileSearch',
134
- 'fuzzyFileSearch/sessionStart',
135
- 'fuzzyFileSearch/sessionUpdate',
136
- 'fuzzyFileSearch/sessionStop',
137
- ]);
138
-
139
- const CODEX_APP_SERVER_SERVER_REQUEST_METHODS = Object.freeze([
140
- 'item/commandExecution/requestApproval',
141
- 'item/fileChange/requestApproval',
142
- 'item/tool/requestUserInput',
143
- 'mcpServer/elicitation/request',
144
- 'item/permissions/requestApproval',
145
- 'item/tool/call',
146
- 'account/chatgptAuthTokens/refresh',
147
- 'attestation/generate',
148
- 'currentTime/read',
149
- 'applyPatchApproval',
150
- 'execCommandApproval',
151
- ]);
152
-
153
- const CODEX_APP_SERVER_NOTIFICATION_METHODS = Object.freeze([
154
- 'error',
155
- 'thread/started',
156
- 'thread/status/changed',
157
- 'thread/archived',
158
- 'thread/deleted',
159
- 'thread/unarchived',
160
- 'thread/closed',
161
- 'skills/changed',
162
- 'thread/name/updated',
163
- 'thread/goal/updated',
164
- 'thread/goal/cleared',
165
- 'thread/settings/updated',
166
- 'thread/tokenUsage/updated',
167
- 'turn/started',
168
- 'hook/started',
169
- 'turn/completed',
170
- 'hook/completed',
171
- 'turn/diff/updated',
172
- 'turn/plan/updated',
173
- 'item/started',
174
- 'item/autoApprovalReview/started',
175
- 'item/autoApprovalReview/completed',
176
- 'item/completed',
177
- 'rawResponseItem/completed',
178
- 'item/agentMessage/delta',
179
- 'item/plan/delta',
180
- 'command/exec/outputDelta',
181
- 'process/outputDelta',
182
- 'process/exited',
183
- 'item/commandExecution/outputDelta',
184
- 'item/commandExecution/terminalInteraction',
185
- 'item/fileChange/outputDelta',
186
- 'item/fileChange/patchUpdated',
187
- 'serverRequest/resolved',
188
- 'item/mcpToolCall/progress',
189
- 'mcpServer/oauthLogin/completed',
190
- 'mcpServer/startupStatus/updated',
191
- 'account/updated',
192
- 'account/rateLimits/updated',
193
- 'app/list/updated',
194
- 'remoteControl/status/changed',
195
- 'externalAgentConfig/import/progress',
196
- 'externalAgentConfig/import/completed',
197
- 'fs/changed',
198
- 'item/reasoning/summaryTextDelta',
199
- 'item/reasoning/summaryPartAdded',
200
- 'item/reasoning/textDelta',
201
- 'thread/compacted',
202
- 'model/rerouted',
203
- 'model/verification',
204
- 'turn/moderationMetadata',
205
- 'model/safetyBuffering/updated',
206
- 'warning',
207
- 'guardianWarning',
208
- 'deprecationNotice',
209
- 'configWarning',
210
- 'fuzzyFileSearch/sessionUpdated',
211
- 'fuzzyFileSearch/sessionCompleted',
212
- 'thread/realtime/started',
213
- 'thread/realtime/itemAdded',
214
- 'thread/realtime/transcript/delta',
215
- 'thread/realtime/transcript/done',
216
- 'thread/realtime/outputAudio/delta',
217
- 'thread/realtime/sdp',
218
- 'thread/realtime/error',
219
- 'thread/realtime/closed',
220
- 'windows/worldWritableWarning',
221
- 'windowsSandbox/setupCompleted',
222
- 'account/login/completed',
223
- ]);
224
-
225
- const CODEX_APP_SERVER_METHOD_SET = new Set(CODEX_APP_SERVER_CLIENT_METHODS);
226
-
227
- class AppServerApiError extends Error {
228
- constructor(status, message, details = undefined) {
229
- super(message);
230
- this.status = status;
231
- this.details = details;
232
- }
233
- }
234
-
235
- function nowIso() {
236
- return new Date().toISOString();
237
- }
238
-
239
- function clampTimeoutMs(value, fallback = DEFAULT_REQUEST_TIMEOUT_MS) {
240
- const timeoutMs = Math.floor(Number(value));
241
- if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return fallback;
242
- return Math.min(timeoutMs, MAX_REQUEST_TIMEOUT_MS);
243
- }
244
-
245
- function normalizeProvider(provider) {
246
- return String(provider || '').trim().toLowerCase() === DEFAULT_PROVIDER ? DEFAULT_PROVIDER : '';
247
- }
248
-
249
- function normalizeEndpoint(endpoint, fallbackEndpoint = '') {
250
- const value = String(endpoint || fallbackEndpoint || '').trim();
251
- if (!value) {
252
- throw new AppServerApiError(
253
- 400,
254
- 'Codex app-server endpoint is required. Start Codex with `codex app-server --listen ws://127.0.0.1:4500` or set FARMING_CODEX_APP_SERVER_ENDPOINT.'
255
- );
256
- }
257
- if (/^wss?:\/\//i.test(value)) return value;
258
- if (/^unix:\/\//i.test(value)) {
259
- const socketPath = value.slice('unix://'.length);
260
- if (!socketPath || !path.isAbsolute(socketPath)) {
261
- throw new AppServerApiError(400, 'Explicit Unix socket endpoints must look like unix:///absolute/path.sock');
262
- }
263
- return value;
264
- }
265
- throw new AppServerApiError(400, 'Codex app-server endpoint must be ws://, wss://, or unix:///absolute/path.sock');
266
- }
267
-
268
- function bearerTokenFromRequest(req, body = {}) {
269
- if (body && typeof body.authToken === 'string' && body.authToken.trim()) {
270
- return body.authToken.trim();
271
- }
272
-
273
- const appServerToken = req.get('x-app-server-auth-token');
274
- if (appServerToken && appServerToken.trim()) return appServerToken.trim();
275
-
276
- const authorization = req.get('authorization') || '';
277
- const match = authorization.match(/^Bearer\s+(.+)$/i);
278
- return match ? match[1].trim() : '';
279
- }
280
-
281
- function tokenFingerprint(token) {
282
- if (!token) return 'none';
283
- return crypto.createHash('sha256').update(token).digest('hex').slice(0, 16);
284
- }
285
-
286
- function requestIdKey(id) {
287
- return typeof id === 'string' ? id : JSON.stringify(id);
288
- }
289
-
290
- function safeJsonForSse(value) {
291
- return JSON.stringify(value).replace(/\u2028|\u2029/g, '');
292
- }
293
-
294
- function sendSseEvent(res, eventName, payload) {
295
- res.write(`event: ${eventName}\n`);
296
- res.write(`data: ${safeJsonForSse(payload)}\n\n`);
297
- }
298
-
299
- function isJsonRpcResponse(message) {
300
- return message && Object.prototype.hasOwnProperty.call(message, 'id') &&
301
- (Object.prototype.hasOwnProperty.call(message, 'result') || Object.prototype.hasOwnProperty.call(message, 'error'));
302
- }
303
-
304
- function isJsonRpcServerRequest(message) {
305
- return message && Object.prototype.hasOwnProperty.call(message, 'id') &&
306
- typeof message.method === 'string' &&
307
- !Object.prototype.hasOwnProperty.call(message, 'result') &&
308
- !Object.prototype.hasOwnProperty.call(message, 'error');
309
- }
310
-
311
- function isJsonRpcNotification(message) {
312
- return message && !Object.prototype.hasOwnProperty.call(message, 'id') &&
313
- typeof message.method === 'string';
314
- }
315
-
316
- class AppServerConnection {
317
- constructor(options) {
318
- this.provider = options.provider;
319
- this.endpoint = options.endpoint;
320
- this.authToken = options.authToken || '';
321
- this.experimentalApi = options.experimentalApi !== false;
322
- this.clientInfo = options.clientInfo;
323
- this.initializeTimeoutMs = options.initializeTimeoutMs || DEFAULT_INITIALIZE_TIMEOUT_MS;
324
- this.requestCounter = 1;
325
- this.eventCounter = 1;
326
- this.pendingRequests = new Map();
327
- this.pendingServerRequests = new Map();
328
- this.eventSubscribers = new Set();
329
- this.recentEvents = [];
330
- this.ws = null;
331
- this.connectPromise = null;
332
- this.connectedAt = null;
333
- this.closedAt = null;
334
- this.lastError = null;
335
- this.serverInfo = null;
336
- }
337
-
338
- status() {
339
- return {
340
- provider: this.provider,
341
- endpoint: this.endpoint,
342
- connected: this.ws && this.ws.readyState === WebSocket.OPEN,
343
- connecting: !!this.connectPromise && !(this.ws && this.ws.readyState === WebSocket.OPEN),
344
- connectedAt: this.connectedAt,
345
- closedAt: this.closedAt,
346
- lastError: this.lastError,
347
- serverInfo: this.serverInfo,
348
- pendingRequestCount: this.pendingRequests.size,
349
- pendingServerRequestIds: Array.from(this.pendingServerRequests.keys()),
350
- recentEventCount: this.recentEvents.length,
351
- };
352
- }
353
-
354
- async connect() {
355
- if (this.ws && this.ws.readyState === WebSocket.OPEN) return this;
356
- if (this.connectPromise) return this.connectPromise;
357
-
358
- this.connectPromise = this.open().finally(() => {
359
- this.connectPromise = null;
360
- });
361
- return this.connectPromise;
362
- }
363
-
364
- async open() {
365
- const ws = await this.openWebSocket();
366
- this.ws = ws;
367
- this.closedAt = null;
368
- this.lastError = null;
369
- this.attachWebSocketHandlers(ws);
370
-
371
- const initializeResult = await this.sendRequest('initialize', {
372
- clientInfo: this.clientInfo,
373
- capabilities: {
374
- experimentalApi: this.experimentalApi,
375
- requestAttestation: false,
376
- mcpServerOpenaiFormElicitation: true,
377
- },
378
- }, {
379
- id: 'initialize',
380
- timeoutMs: this.initializeTimeoutMs,
381
- allowInitialize: true,
382
- });
383
- if (initializeResult.error) {
384
- throw new AppServerApiError(502, `Codex app-server initialize failed: ${initializeResult.error.message || 'unknown error'}`, initializeResult.error);
385
- }
386
-
387
- this.serverInfo = initializeResult.result || null;
388
- this.connectedAt = nowIso();
389
- this.sendRaw({ method: 'initialized' });
390
- this.emitEvent('transport', {
391
- state: 'connected',
392
- serverInfo: this.serverInfo,
393
- });
394
- return this;
395
- }
396
-
397
- openWebSocket() {
398
- const headers = this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {};
399
- const endpoint = this.endpoint;
400
-
401
- return new Promise((resolve, reject) => {
402
- const timeout = setTimeout(() => {
403
- reject(new AppServerApiError(504, `Timed out connecting to Codex app-server at ${endpoint}`));
404
- }, this.initializeTimeoutMs);
405
- if (typeof timeout.unref === 'function') timeout.unref();
406
-
407
- const handleOpen = (ws) => {
408
- clearTimeout(timeout);
409
- resolve(ws);
410
- };
411
- const handleError = (error) => {
412
- clearTimeout(timeout);
413
- reject(new AppServerApiError(502, `Failed to connect to Codex app-server: ${error.message || error}`));
414
- };
415
-
416
- let ws;
417
- if (endpoint.startsWith('unix://')) {
418
- const socketPath = endpoint.slice('unix://'.length);
419
- // `ws` deliberately ignores a `socketPath` option for ws:// URLs.
420
- // Its documented IPC transport is ws+unix://<socket-path>:/<request-path>.
421
- // Codex uses `/` for its control-socket WebSocket upgrade.
422
- ws = new WebSocket(`ws+unix://${socketPath}:/`, {
423
- headers,
424
- // Codex's Unix control socket uses tokio-tungstenite without
425
- // permessage-deflate negotiation. `ws` enables it by default,
426
- // which makes the server reject the upgrade before initialize.
427
- perMessageDeflate: false,
428
- });
429
- } else {
430
- ws = new WebSocket(endpoint, { headers, perMessageDeflate: false });
431
- }
432
- ws.once('open', () => handleOpen(ws));
433
- ws.once('error', handleError);
434
- });
435
- }
436
-
437
- attachWebSocketHandlers(ws) {
438
- ws.on('message', (data) => {
439
- this.handleMessage(data);
440
- });
441
- ws.on('error', (error) => {
442
- this.lastError = error.message || String(error);
443
- this.emitEvent('transport', {
444
- state: 'error',
445
- error: this.lastError,
446
- });
447
- });
448
- ws.on('close', (code, reasonBuffer) => {
449
- const reason = reasonBuffer ? reasonBuffer.toString() : '';
450
- this.closedAt = nowIso();
451
- this.emitEvent('transport', {
452
- state: 'closed',
453
- code,
454
- reason,
455
- });
456
- this.rejectPendingRequests({
457
- code: -32000,
458
- message: reason || 'Codex app-server connection closed',
459
- });
460
- });
461
- }
462
-
463
- handleMessage(data) {
464
- let message;
465
- try {
466
- message = JSON.parse(data.toString());
467
- } catch (error) {
468
- this.emitEvent('parse-error', {
469
- error: error.message || 'Failed to parse Codex app-server message',
470
- });
471
- return;
472
- }
473
-
474
- if (isJsonRpcResponse(message)) {
475
- this.handleResponse(message);
476
- return;
477
- }
478
-
479
- if (isJsonRpcServerRequest(message)) {
480
- const key = requestIdKey(message.id);
481
- this.pendingServerRequests.set(key, {
482
- id: message.id,
483
- method: message.method,
484
- params: message.params,
485
- receivedAt: nowIso(),
486
- });
487
- this.emitEvent('server-request', message);
488
- return;
489
- }
490
-
491
- if (isJsonRpcNotification(message)) {
492
- this.emitEvent('notification', message);
493
- return;
494
- }
495
-
496
- this.emitEvent('unknown-message', { message });
497
- }
498
-
499
- handleResponse(message) {
500
- const key = requestIdKey(message.id);
501
- const pending = this.pendingRequests.get(key);
502
- if (!pending) {
503
- this.emitEvent('response', message);
504
- return;
505
- }
506
-
507
- clearTimeout(pending.timeout);
508
- this.pendingRequests.delete(key);
509
- pending.resolve(message);
510
- }
511
-
512
- rejectPendingRequests(error) {
513
- this.pendingRequests.forEach((pending, key) => {
514
- clearTimeout(pending.timeout);
515
- pending.resolve({
516
- id: pending.id,
517
- error,
518
- });
519
- this.pendingRequests.delete(key);
520
- });
521
- }
522
-
523
- sendRaw(message) {
524
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
525
- throw new AppServerApiError(502, 'Codex app-server connection is not open');
526
- }
527
- this.ws.send(JSON.stringify(message));
528
- }
529
-
530
- sendRequest(method, params, options = {}) {
531
- if (!options.allowInitialize && !CODEX_APP_SERVER_METHOD_SET.has(method)) {
532
- throw new AppServerApiError(400, `Unsupported Codex app-server method: ${method}`);
533
- }
534
-
535
- const id = options.id || `farming-${this.requestCounter++}`;
536
- const message = { id, method };
537
- if (params !== undefined) message.params = params;
538
- const timeoutMs = clampTimeoutMs(options.timeoutMs);
539
-
540
- return new Promise((resolve, reject) => {
541
- let timeout = null;
542
- try {
543
- timeout = setTimeout(() => {
544
- this.pendingRequests.delete(requestIdKey(id));
545
- resolve({
546
- id,
547
- error: {
548
- code: -32002,
549
- message: `Timed out waiting for ${method} response`,
550
- },
551
- });
552
- }, timeoutMs);
553
- if (typeof timeout.unref === 'function') timeout.unref();
554
- this.pendingRequests.set(requestIdKey(id), { id, method, resolve, timeout });
555
- this.sendRaw(message);
556
- } catch (error) {
557
- if (timeout) clearTimeout(timeout);
558
- this.pendingRequests.delete(requestIdKey(id));
559
- reject(error);
560
- }
561
- });
562
- }
563
-
564
- async request(method, params, options = {}) {
565
- await this.connect();
566
- return this.sendRequest(method, params, options);
567
- }
568
-
569
- resolveServerRequest(rawRequestId, result) {
570
- const key = String(rawRequestId || '');
571
- const request = this.pendingServerRequests.get(key);
572
- if (!request) {
573
- throw new AppServerApiError(404, 'Codex app-server server request not found');
574
- }
575
- this.sendRaw({ id: request.id, result: result === undefined ? null : result });
576
- this.pendingServerRequests.delete(key);
577
- return { id: request.id, resolved: true };
578
- }
579
-
580
- rejectServerRequest(rawRequestId, error) {
581
- const key = String(rawRequestId || '');
582
- const request = this.pendingServerRequests.get(key);
583
- if (!request) {
584
- throw new AppServerApiError(404, 'Codex app-server server request not found');
585
- }
586
- this.sendRaw({
587
- id: request.id,
588
- error: {
589
- code: Number.isFinite(Number(error && error.code)) ? Number(error.code) : -32000,
590
- message: String(error && error.message ? error.message : 'Rejected by Farming app-server API'),
591
- ...(error && error.data !== undefined ? { data: error.data } : {}),
592
- },
593
- });
594
- this.pendingServerRequests.delete(key);
595
- return { id: request.id, rejected: true };
596
- }
597
-
598
- subscribe(handler, options = {}) {
599
- if (options.replay !== false) {
600
- this.recentEvents.forEach(handler);
601
- }
602
- this.eventSubscribers.add(handler);
603
- return () => {
604
- this.eventSubscribers.delete(handler);
605
- };
606
- }
607
-
608
- emitEvent(kind, payload) {
609
- const event = {
610
- id: this.eventCounter++,
611
- at: nowIso(),
612
- provider: this.provider,
613
- endpoint: this.endpoint,
614
- kind,
615
- payload,
616
- };
617
- this.recentEvents.push(event);
618
- if (this.recentEvents.length > EVENT_BUFFER_LIMIT) {
619
- this.recentEvents.splice(0, this.recentEvents.length - EVENT_BUFFER_LIMIT);
620
- }
621
- this.eventSubscribers.forEach((handler) => {
622
- try {
623
- handler(event);
624
- } catch {
625
- // Event subscribers are HTTP response writers; a failed subscriber should
626
- // not break the app-server transport connection.
627
- }
628
- });
629
- }
630
-
631
- close() {
632
- if (this.ws) {
633
- this.ws.close();
634
- this.ws = null;
635
- }
636
- this.rejectPendingRequests({
637
- code: -32000,
638
- message: 'Codex app-server connection closed by Farming',
639
- });
640
- }
641
- }
642
-
643
- class AppServerApiBridge {
644
- constructor(options = {}) {
645
- this.defaultCodexEndpoint = options.defaultCodexEndpoint || process.env.FARMING_CODEX_APP_SERVER_ENDPOINT || '';
646
- this.clientInfo = {
647
- name: 'farming',
648
- title: 'Farming app-server API',
649
- version: packageJson.version || '0.0.0',
650
- };
651
- this.connections = new Map();
652
- }
653
-
654
- metadata() {
655
- return {
656
- providers: [DEFAULT_PROVIDER],
657
- codex: {
658
- transportManagedMethods: ['initialize', 'initialized'],
659
- clientMethods: CODEX_APP_SERVER_CLIENT_METHODS,
660
- serverRequestMethods: CODEX_APP_SERVER_SERVER_REQUEST_METHODS,
661
- notificationMethods: CODEX_APP_SERVER_NOTIFICATION_METHODS,
662
- defaultEndpointConfigured: !!this.defaultCodexEndpoint,
663
- },
664
- };
665
- }
666
-
667
- connectionKey(provider, endpoint, authToken) {
668
- return `${provider}\0${endpoint}\0${tokenFingerprint(authToken)}`;
669
- }
670
-
671
- getConnection(provider, options = {}) {
672
- const normalizedProvider = normalizeProvider(provider);
673
- if (!normalizedProvider) {
674
- throw new AppServerApiError(404, 'Only the codex app-server provider is supported');
675
- }
676
- const endpoint = normalizeEndpoint(options.endpoint, this.defaultCodexEndpoint);
677
- const authToken = options.authToken || '';
678
- const key = this.connectionKey(normalizedProvider, endpoint, authToken);
679
- let connection = this.connections.get(key);
680
- if (!connection) {
681
- connection = new AppServerConnection({
682
- provider: normalizedProvider,
683
- endpoint,
684
- authToken,
685
- experimentalApi: options.experimentalApi,
686
- clientInfo: this.clientInfo,
687
- });
688
- this.connections.set(key, connection);
689
- }
690
- return connection;
691
- }
692
-
693
- getStatus(provider, options = {}) {
694
- const normalizedProvider = normalizeProvider(provider);
695
- if (!normalizedProvider) {
696
- throw new AppServerApiError(404, 'Only the codex app-server provider is supported');
697
- }
698
- const endpoint = normalizeEndpoint(options.endpoint, this.defaultCodexEndpoint);
699
- const authToken = options.authToken || '';
700
- const connection = this.connections.get(this.connectionKey(normalizedProvider, endpoint, authToken));
701
- return connection
702
- ? connection.status()
703
- : {
704
- provider: normalizedProvider,
705
- endpoint,
706
- connected: false,
707
- connecting: false,
708
- connectedAt: null,
709
- closedAt: null,
710
- lastError: null,
711
- serverInfo: null,
712
- pendingRequestCount: 0,
713
- pendingServerRequestIds: [],
714
- recentEventCount: 0,
715
- };
716
- }
717
-
718
- async connect(provider, options = {}) {
719
- const connection = this.getConnection(provider, options);
720
- await connection.connect();
721
- return connection.status();
722
- }
723
-
724
- async request(provider, method, params, options = {}) {
725
- const connection = this.getConnection(provider, options);
726
- return connection.request(method, params, {
727
- timeoutMs: options.timeoutMs,
728
- });
729
- }
730
-
731
- resolveServerRequest(provider, requestId, result, options = {}) {
732
- const connection = this.getConnection(provider, options);
733
- return connection.resolveServerRequest(requestId, result);
734
- }
735
-
736
- rejectServerRequest(provider, requestId, error, options = {}) {
737
- const connection = this.getConnection(provider, options);
738
- return connection.rejectServerRequest(requestId, error);
739
- }
740
-
741
- disconnect(provider, options = {}) {
742
- const connection = this.getConnection(provider, options);
743
- connection.close();
744
- return connection.status();
745
- }
746
- }
747
-
748
- function sendError(res, error) {
749
- if (error instanceof AppServerApiError) {
750
- res.status(error.status).json({
751
- error: error.message,
752
- ...(error.details !== undefined ? { details: error.details } : {}),
753
- });
754
- return;
755
- }
756
- res.status(500).json({ error: error.message || 'Farming app-server API failed' });
757
- }
758
-
759
- function requestOptions(req, body = {}) {
760
- return {
761
- endpoint: body.endpoint || req.query.endpoint,
762
- authToken: bearerTokenFromRequest(req, body),
763
- experimentalApi: body.experimentalApi !== false,
764
- timeoutMs: body.timeoutMs,
765
- };
766
- }
767
-
768
- function createAppServerApiRouter(options = {}) {
769
- const bridge = options.bridge || new AppServerApiBridge(options);
770
- const router = express.Router();
771
-
772
- router.use(express.json({ limit: '2mb' }));
773
-
774
- router.get('/', (_req, res) => {
775
- res.json(bridge.metadata());
776
- });
777
-
778
- router.get('/:provider', (req, res) => {
779
- try {
780
- const provider = normalizeProvider(req.params.provider);
781
- if (!provider) throw new AppServerApiError(404, 'Only the codex app-server provider is supported');
782
- const options = requestOptions(req);
783
- const hasEndpoint = !!(options.endpoint || bridge.defaultCodexEndpoint);
784
- res.json({
785
- ...bridge.metadata()[provider],
786
- status: hasEndpoint
787
- ? bridge.getStatus(provider, options)
788
- : {
789
- provider,
790
- endpoint: null,
791
- connected: false,
792
- connecting: false,
793
- endpointRequired: true,
794
- },
795
- });
796
- } catch (error) {
797
- sendError(res, error);
798
- }
799
- });
800
-
801
- router.post('/:provider/connect', async (req, res) => {
802
- try {
803
- const status = await bridge.connect(req.params.provider, requestOptions(req, req.body || {}));
804
- res.json({ status });
805
- } catch (error) {
806
- sendError(res, error);
807
- }
808
- });
809
-
810
- router.post('/:provider/disconnect', (req, res) => {
811
- try {
812
- const status = bridge.disconnect(req.params.provider, requestOptions(req, req.body || {}));
813
- res.json({ status });
814
- } catch (error) {
815
- sendError(res, error);
816
- }
817
- });
818
-
819
- router.post('/:provider/rpc', async (req, res) => {
820
- const body = req.body || {};
821
- try {
822
- const method = String(body.method || '').trim();
823
- if (!method) throw new AppServerApiError(400, 'method is required');
824
- const response = await bridge.request(req.params.provider, method, body.params, requestOptions(req, body));
825
- res.json(response);
826
- } catch (error) {
827
- sendError(res, error);
828
- }
829
- });
830
-
831
- router.post('/:provider/server-requests/:requestId/resolve', (req, res) => {
832
- try {
833
- const body = req.body || {};
834
- const response = bridge.resolveServerRequest(
835
- req.params.provider,
836
- req.params.requestId,
837
- Object.prototype.hasOwnProperty.call(body, 'result') ? body.result : null,
838
- requestOptions(req, body)
839
- );
840
- res.json(response);
841
- } catch (error) {
842
- sendError(res, error);
843
- }
844
- });
845
-
846
- router.post('/:provider/server-requests/:requestId/reject', (req, res) => {
847
- try {
848
- const body = req.body || {};
849
- const response = bridge.rejectServerRequest(
850
- req.params.provider,
851
- req.params.requestId,
852
- body.error || {},
853
- requestOptions(req, body)
854
- );
855
- res.json(response);
856
- } catch (error) {
857
- sendError(res, error);
858
- }
859
- });
860
-
861
- router.get('/:provider/events', async (req, res) => {
862
- let heartbeat = null;
863
- let unsubscribe = null;
864
-
865
- try {
866
- res.writeHead(200, {
867
- 'Content-Type': 'text/event-stream',
868
- 'Cache-Control': 'no-cache, no-transform',
869
- Connection: 'keep-alive',
870
- 'X-Accel-Buffering': 'no',
871
- });
872
- sendSseEvent(res, 'ready', {
873
- provider: req.params.provider,
874
- at: nowIso(),
875
- });
876
-
877
- const connection = bridge.getConnection(req.params.provider, requestOptions(req));
878
- unsubscribe = connection.subscribe((event) => {
879
- sendSseEvent(res, event.kind, event);
880
- }, {
881
- replay: req.query.replay !== '0',
882
- });
883
- await connection.connect();
884
- heartbeat = setInterval(() => {
885
- sendSseEvent(res, 'heartbeat', { at: nowIso() });
886
- }, 25_000);
887
- if (typeof heartbeat.unref === 'function') heartbeat.unref();
888
- } catch (error) {
889
- sendSseEvent(res, 'error', {
890
- at: nowIso(),
891
- error: error.message || 'Farming app-server API event stream failed',
892
- });
893
- res.end();
894
- if (unsubscribe) unsubscribe();
895
- if (heartbeat) clearInterval(heartbeat);
896
- return;
897
- }
898
-
899
- req.on('close', () => {
900
- if (unsubscribe) unsubscribe();
901
- if (heartbeat) clearInterval(heartbeat);
902
- });
903
- });
904
-
905
- return router;
906
- }
907
-
908
- module.exports = {
909
- AppServerConnection,
910
- AppServerApiBridge,
911
- AppServerApiError,
912
- CODEX_APP_SERVER_CLIENT_METHODS,
913
- CODEX_APP_SERVER_NOTIFICATION_METHODS,
914
- CODEX_APP_SERVER_SERVER_REQUEST_METHODS,
915
- createAppServerApiRouter,
916
- normalizeEndpoint,
917
- };