@legendapp/spark 0.0.1-next.2 → 0.0.1-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (399) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -4
  3. package/ai-codex.ts +1 -0
  4. package/ai.ts +1 -0
  5. package/app-documents.ts +1 -0
  6. package/config-plugin.d.cts +2 -0
  7. package/contracts.ts +2 -0
  8. package/diagnostics.ts +1 -0
  9. package/expo-config.d.cts +1 -0
  10. package/metro.cjs +4 -1
  11. package/metro.d.cts +12 -0
  12. package/native.cjs +3 -1
  13. package/native.d.cts +6 -0
  14. package/package.json +82 -14
  15. package/schema.json +276 -58
  16. package/settings-observable.ts +1 -0
  17. package/settings-window.ts +1 -0
  18. package/shortcuts-commands.ts +1 -0
  19. package/shortcuts-keyboard.ts +3 -0
  20. package/ui-classnames.ts +1 -0
  21. package/ui-glass.ts +1 -0
  22. package/ui-search.ts +1 -0
  23. package/ui-sidebar.ts +1 -0
  24. package/ui-split-view.ts +1 -0
  25. package/ui-symbol.ts +1 -0
  26. package/vendor/node_modules/@legendapp/spark-ai/package.json +19 -0
  27. package/vendor/node_modules/@legendapp/spark-ai/src/availability.ts +17 -0
  28. package/vendor/node_modules/@legendapp/spark-ai/src/index.ts +15 -0
  29. package/vendor/node_modules/@legendapp/spark-ai/src/invocations.ts +34 -0
  30. package/vendor/node_modules/@legendapp/spark-ai/src/output.ts +62 -0
  31. package/vendor/node_modules/@legendapp/spark-ai/src/run.ts +12 -0
  32. package/vendor/node_modules/@legendapp/spark-ai/src/types.ts +16 -0
  33. package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.h +2 -1
  34. package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.mm +126 -24
  35. package/vendor/node_modules/@legendapp/spark-audio/package.json +5 -2
  36. package/vendor/node_modules/@legendapp/spark-audio/src/NativeSparkAudio.ts +5 -1
  37. package/vendor/node_modules/@legendapp/spark-audio/src/desktop.ts +36 -32
  38. package/vendor/node_modules/@legendapp/spark-audio/src/events.ts +11 -0
  39. package/vendor/node_modules/@legendapp/spark-audio/src/hooks.ts +65 -0
  40. package/vendor/node_modules/@legendapp/spark-audio/src/index.web.ts +92 -33
  41. package/vendor/node_modules/@legendapp/spark-audio/src/media-session-unavailable.ts +6 -4
  42. package/vendor/node_modules/@legendapp/spark-audio/src/media-session.ts +52 -19
  43. package/vendor/node_modules/@legendapp/spark-audio/src/media-session.web.ts +18 -10
  44. package/vendor/node_modules/@legendapp/spark-audio/src/media-types.ts +40 -13
  45. package/vendor/node_modules/@legendapp/spark-audio/src/mobile.ts +40 -32
  46. package/vendor/node_modules/@legendapp/spark-audio/src/player.ts +97 -0
  47. package/vendor/node_modules/@legendapp/spark-audio/src/status-listeners.ts +21 -15
  48. package/vendor/node_modules/@legendapp/spark-audio/src/types.ts +9 -4
  49. package/vendor/node_modules/@legendapp/spark-audio/windows/SparkAudio/SparkAudio.h +67 -20
  50. package/vendor/node_modules/@legendapp/spark-auth-session/package.json +4 -3
  51. package/vendor/node_modules/@legendapp/spark-auth-session/src/core.ts +60 -42
  52. package/vendor/node_modules/@legendapp/spark-auth-session/src/index.ts +28 -12
  53. package/vendor/node_modules/@legendapp/spark-auth-session/src/mobile.ts +97 -41
  54. package/vendor/node_modules/@legendapp/spark-auth-session/src/types.ts +20 -9
  55. package/vendor/node_modules/@legendapp/spark-cli/dist/add-desktop.js +50 -7
  56. package/vendor/node_modules/@legendapp/spark-cli/dist/build-mode.js +1 -1
  57. package/vendor/node_modules/@legendapp/spark-cli/dist/build.js +83 -48
  58. package/vendor/node_modules/@legendapp/spark-cli/dist/cli-arguments.js +60 -0
  59. package/vendor/node_modules/@legendapp/spark-cli/dist/commands.js +7 -2
  60. package/vendor/node_modules/@legendapp/spark-cli/dist/create.js +31 -5
  61. package/vendor/node_modules/@legendapp/spark-cli/dist/credentials.js +69 -23
  62. package/vendor/node_modules/@legendapp/spark-cli/dist/dev-command.js +12 -3
  63. package/vendor/node_modules/@legendapp/spark-cli/dist/dev.js +12 -9
  64. package/vendor/node_modules/@legendapp/spark-cli/dist/examples.js +1 -1
  65. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-dev-patch.cjs +4 -2
  66. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-metro.cjs +1 -1
  67. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-node.js +3 -3
  68. package/vendor/node_modules/@legendapp/spark-cli/dist/index.js +10 -38
  69. package/vendor/node_modules/@legendapp/spark-cli/dist/local.js +1 -1
  70. package/vendor/node_modules/@legendapp/spark-cli/dist/metro.cjs +26 -5
  71. package/vendor/node_modules/@legendapp/spark-cli/dist/native-metadata.js +26 -0
  72. package/vendor/node_modules/@legendapp/spark-cli/dist/native-patch-preflight.js +84 -0
  73. package/vendor/node_modules/@legendapp/spark-cli/dist/native-preparation.js +88 -0
  74. package/vendor/node_modules/@legendapp/spark-cli/dist/notary-profiles.js +75 -0
  75. package/vendor/node_modules/@legendapp/spark-cli/dist/package.js +4 -2
  76. package/vendor/node_modules/@legendapp/spark-cli/dist/platform.js +16 -2
  77. package/vendor/node_modules/@legendapp/spark-cli/dist/project.js +66 -13
  78. package/vendor/node_modules/@legendapp/spark-cli/dist/release.js +84 -4
  79. package/vendor/node_modules/@legendapp/spark-cli/dist/release.json +23 -18
  80. package/vendor/node_modules/@legendapp/spark-cli/dist/runner-install.js +145 -31
  81. package/vendor/node_modules/@legendapp/spark-cli/dist/signing.js +4 -3
  82. package/vendor/node_modules/@legendapp/spark-cli/dist/updates.js +41 -4
  83. package/vendor/node_modules/@legendapp/spark-cli/dist/windows.js +4 -1
  84. package/vendor/node_modules/@legendapp/spark-cli/package.json +7 -6
  85. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/App.tsx +6 -12
  86. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/desktop.config.json +5 -3
  87. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/package.json +11 -9
  88. package/vendor/node_modules/@legendapp/spark-cli/templates/diff-lite/Git.macos.tsx +2 -2
  89. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.macos.tsx +23 -21
  90. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.windows.tsx +23 -21
  91. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.desktop.ts +11 -8
  92. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.macos.ts +3 -3
  93. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/Lifecycle.desktop.tsx +8 -14
  94. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/OpenFiles.desktop.tsx +5 -10
  95. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.desktop.ts +11 -8
  96. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.macos.ts +3 -3
  97. package/vendor/node_modules/@legendapp/spark-cli/templates/music-lite/assets.desktop.ts +2 -2
  98. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Activation.desktop.tsx +2 -2
  99. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Desktop.desktop.ts +37 -15
  100. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/session.ts +5 -6
  101. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/window-session.ts +5 -4
  102. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/desktop.config.json +5 -3
  103. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/metro.config.js +1 -1
  104. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/package.json +13 -11
  105. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/react-native.config.js +1 -1
  106. package/vendor/node_modules/@legendapp/spark-cli/templates/windows/package.json +13 -11
  107. package/vendor/node_modules/@legendapp/spark-cli/templates/windows/react-native.config.js +1 -1
  108. package/vendor/node_modules/@legendapp/spark-clipboard/macos/RNDesktopClipboard.mm +10 -16
  109. package/vendor/node_modules/@legendapp/spark-clipboard/package.json +2 -2
  110. package/vendor/node_modules/@legendapp/spark-clipboard/src/NativeDesktopClipboard.ts +1 -1
  111. package/vendor/node_modules/@legendapp/spark-clipboard/src/desktop.ts +55 -31
  112. package/vendor/node_modules/@legendapp/spark-clipboard/src/expo.ts +21 -11
  113. package/vendor/node_modules/@legendapp/spark-clipboard/src/options.ts +8 -0
  114. package/vendor/node_modules/@legendapp/spark-clipboard/src/types.ts +10 -0
  115. package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.h +38 -15
  116. package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.vcxproj +2 -0
  117. package/vendor/node_modules/@legendapp/spark-codex/RNCodex.podspec +22 -0
  118. package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.hpp +23 -0
  119. package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.mm +1529 -0
  120. package/vendor/node_modules/@legendapp/spark-codex/nitro.json +21 -0
  121. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/.gitattributes +1 -0
  122. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex+autolinking.rb +62 -0
  123. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.cpp +17 -0
  124. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.hpp +27 -0
  125. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Umbrella.hpp +38 -0
  126. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.mm +35 -0
  127. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.swift +16 -0
  128. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexAvailability.hpp +95 -0
  129. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexRunResult.hpp +99 -0
  130. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.cpp +24 -0
  131. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.hpp +71 -0
  132. package/vendor/node_modules/@legendapp/spark-codex/package.json +35 -0
  133. package/vendor/node_modules/@legendapp/spark-codex/react-native.config.js +11 -0
  134. package/vendor/node_modules/@legendapp/spark-codex/src/Codex.nitro.ts +30 -0
  135. package/vendor/node_modules/@legendapp/spark-codex/src/index.ts +65 -0
  136. package/vendor/node_modules/@legendapp/spark-codex/src/native.ts +6 -0
  137. package/vendor/node_modules/@legendapp/spark-commands/package.json +21 -0
  138. package/vendor/node_modules/@legendapp/spark-commands/src/bindings.ts +90 -0
  139. package/vendor/node_modules/@legendapp/spark-commands/src/index.tsx +400 -0
  140. package/vendor/node_modules/@legendapp/spark-commands/src/router.ts +138 -0
  141. package/vendor/node_modules/@legendapp/spark-commands/src/storage.ts +20 -0
  142. package/vendor/node_modules/@legendapp/spark-context-menu/macos/RNContextMenu.mm +7 -8
  143. package/vendor/node_modules/@legendapp/spark-context-menu/package.json +4 -3
  144. package/vendor/node_modules/@legendapp/spark-context-menu/src/NativeContextMenu.ts +1 -1
  145. package/vendor/node_modules/@legendapp/spark-context-menu/src/index.ts +26 -26
  146. package/vendor/node_modules/@legendapp/spark-context-menu/windows/SparkContextMenu/SparkContextMenu.h +3 -2
  147. package/vendor/node_modules/@legendapp/spark-desktop-app/RNDesktopApp.podspec +2 -1
  148. package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryJSI.hpp +89 -0
  149. package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryWindows.hpp +22 -0
  150. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/RNDesktopApp.mm +13 -7
  151. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkBinaryJSI.h +48 -0
  152. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.h +4 -0
  153. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.mm +33 -33
  154. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktopError.h +20 -0
  155. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.h +26 -0
  156. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.mm +49 -0
  157. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.h +9 -0
  158. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.mm +70 -0
  159. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkWindow.mm +9 -6
  160. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/recent-documents/RNRecentDocumentController.mm +55 -0
  161. package/vendor/node_modules/@legendapp/spark-desktop-app/package.json +5 -3
  162. package/vendor/node_modules/@legendapp/spark-desktop-app/src/NativeDesktopApp.ts +1 -1
  163. package/vendor/node_modules/@legendapp/spark-desktop-app/src/api.ts +86 -40
  164. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/accelerator.ts +40 -0
  165. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/index.ts +56 -0
  166. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/menu.ts +114 -0
  167. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/native-buffer.ts +22 -0
  168. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/path.ts +24 -0
  169. package/vendor/node_modules/@legendapp/spark-desktop-app/src/events.ts +75 -0
  170. package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.ts +2 -0
  171. package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.windows.ts +2 -0
  172. package/vendor/node_modules/@legendapp/spark-desktop-app/src/open-requests.ts +43 -0
  173. package/vendor/node_modules/@legendapp/spark-desktop-app/src/restoration.ts +11 -0
  174. package/vendor/node_modules/@legendapp/spark-desktop-app/src/transport.ts +10 -0
  175. package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.cjs +10 -5
  176. package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.d.cts +3 -0
  177. package/vendor/node_modules/@legendapp/spark-desktop-config/autolinking.cjs +35 -0
  178. package/vendor/node_modules/@legendapp/spark-desktop-config/config.cjs +16 -3
  179. package/vendor/node_modules/@legendapp/spark-desktop-config/config.d.cts +89 -9
  180. package/vendor/node_modules/@legendapp/spark-desktop-config/expo.d.cts +3 -0
  181. package/vendor/node_modules/@legendapp/spark-desktop-config/identity.cjs +3 -2
  182. package/vendor/node_modules/@legendapp/spark-desktop-config/lifecycle.cjs +20 -0
  183. package/vendor/node_modules/@legendapp/spark-desktop-config/package.json +6 -5
  184. package/vendor/node_modules/@legendapp/spark-desktop-config/schema.json +276 -58
  185. package/vendor/node_modules/@legendapp/spark-desktop-config/updates.cjs +1 -0
  186. package/vendor/node_modules/@legendapp/spark-desktop-config/validation.cjs +35 -0
  187. package/vendor/node_modules/@legendapp/spark-desktop-config/windows.plugin.cjs +2 -1
  188. package/vendor/node_modules/@legendapp/spark-desktop-host/AppDelegate.mm +129 -11
  189. package/vendor/node_modules/@legendapp/spark-desktop-host/package.json +2 -2
  190. package/vendor/node_modules/@legendapp/spark-desktop-host/windows/application.inc +277 -121
  191. package/vendor/node_modules/@legendapp/spark-desktop-links/common/AuthLoopback.h +5 -7
  192. package/vendor/node_modules/@legendapp/spark-desktop-links/macos/RNDesktopLinks.mm +10 -9
  193. package/vendor/node_modules/@legendapp/spark-desktop-links/package.json +2 -2
  194. package/vendor/node_modules/@legendapp/spark-desktop-links/src/NativeDesktopLinks.ts +1 -1
  195. package/vendor/node_modules/@legendapp/spark-desktop-links/src/api.ts +27 -33
  196. package/vendor/node_modules/@legendapp/spark-desktop-links/src/expo.ts +3 -4
  197. package/vendor/node_modules/@legendapp/spark-desktop-links/src/native.ts +9 -0
  198. package/vendor/node_modules/@legendapp/spark-desktop-links/windows/SparkLinks/SparkLinks.h +22 -22
  199. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/RNDesktopShortcuts.podspec +1 -0
  200. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/RNDesktopShortcuts.mm +1 -1
  201. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.h +9 -0
  202. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.mm +307 -0
  203. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/package.json +8 -5
  204. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/NativeDesktopShortcuts.ts +1 -1
  205. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/api.ts +39 -14
  206. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/NativeKeyboardManager.ts +12 -0
  207. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/codes.ts +108 -0
  208. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/index.ts +99 -0
  209. package/vendor/node_modules/@legendapp/spark-desktop-windows/RNDesktopWindows.podspec +2 -0
  210. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/RNDesktopWindows.mm +229 -103
  211. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/SparkWindowGeometry.h +63 -0
  212. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.h +20 -0
  213. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.mm +27 -0
  214. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.h +12 -0
  215. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.mm +3130 -0
  216. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManagerStartup.mm +31 -0
  217. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/SidebarStartupFallbacks.mm +14 -0
  218. package/vendor/node_modules/@legendapp/spark-desktop-windows/package.json +10 -6
  219. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/NativeDesktopWindowManager.ts +1 -1
  220. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/api.ts +144 -70
  221. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/diagnostics.ts +17 -0
  222. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.ts +3 -1
  223. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.windows.ts +3 -1
  224. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos-adapter.ts +53 -0
  225. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos.ts +51 -0
  226. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/transport.ts +41 -0
  227. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/types.ts +140 -0
  228. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/validation.ts +162 -0
  229. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/window-manager/NativeWindowManager.ts +28 -0
  230. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/WindowProvider.tsx +20 -0
  231. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/createWindowsNavigator.tsx +78 -0
  232. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/index.ts +6 -0
  233. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/primaryWindowLifecycle.ts +34 -0
  234. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/types.ts +11 -0
  235. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/usePrimaryWindowLifecycle.ts +18 -0
  236. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/useWindowFocusEffect.ts +17 -0
  237. package/vendor/node_modules/@legendapp/spark-documents/package.json +22 -0
  238. package/vendor/node_modules/@legendapp/spark-documents/src/controller.ts +97 -0
  239. package/vendor/node_modules/@legendapp/spark-documents/src/documentTransition.ts +13 -0
  240. package/vendor/node_modules/@legendapp/spark-documents/src/hooks.ts +139 -0
  241. package/vendor/node_modules/@legendapp/spark-documents/src/index.tsx +50 -0
  242. package/vendor/node_modules/@legendapp/spark-documents/src/reload.ts +39 -0
  243. package/vendor/node_modules/@legendapp/spark-documents/src/requests.ts +15 -0
  244. package/vendor/node_modules/@legendapp/spark-drag-drop/package.json +4 -3
  245. package/vendor/node_modules/@legendapp/spark-drag-drop/src/DesktopDragViewNativeComponent.ts +1 -1
  246. package/vendor/node_modules/@legendapp/spark-drag-drop/src/contracts.ts +73 -18
  247. package/vendor/node_modules/@legendapp/spark-drag-drop/src/index.tsx +58 -15
  248. package/vendor/node_modules/@legendapp/spark-drag-drop/windows/SparkDragDrop/SparkDragDrop.h +14 -8
  249. package/vendor/node_modules/@legendapp/spark-file-dialog/ios/RNFileDialog.mm +34 -121
  250. package/vendor/node_modules/@legendapp/spark-file-dialog/package.json +12 -5
  251. package/vendor/node_modules/@legendapp/spark-file-dialog/src/NativeFileDialog.ts +1 -5
  252. package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.ts +19 -84
  253. package/vendor/node_modules/@legendapp/spark-file-dialog/src/native.ts +12 -0
  254. package/vendor/node_modules/@legendapp/spark-file-dialog/src/options.ts +34 -14
  255. package/vendor/node_modules/@legendapp/spark-file-dialog/src/types.ts +26 -0
  256. package/vendor/node_modules/@legendapp/spark-file-dialog/windows/SparkFileDialog/SparkFileDialog.h +42 -58
  257. package/vendor/node_modules/@legendapp/spark-file-system/RNDesktopFileSystem.podspec +1 -0
  258. package/vendor/node_modules/@legendapp/spark-file-system/macos/RNDesktopFileSystem.mm +32 -21
  259. package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.h +24 -0
  260. package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.mm +215 -0
  261. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.h +9 -0
  262. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.mm +82 -0
  263. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.h +47 -0
  264. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.mm +306 -0
  265. package/vendor/node_modules/@legendapp/spark-file-system/package.json +8 -6
  266. package/vendor/node_modules/@legendapp/spark-file-system/src/NativeDesktopFileSystem.ts +1 -1
  267. package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/NativeFileScanner.ts +11 -0
  268. package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/index.ts +93 -0
  269. package/vendor/node_modules/@legendapp/spark-file-system/src/handles.ts +48 -14
  270. package/vendor/node_modules/@legendapp/spark-file-system/src/index.ts +134 -45
  271. package/vendor/node_modules/@legendapp/spark-file-system/src/path.ts +1 -9
  272. package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.h +102 -26
  273. package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.vcxproj +2 -0
  274. package/vendor/node_modules/@legendapp/spark-global-shortcuts/RNDesktopGlobalShortcuts.podspec +1 -0
  275. package/vendor/node_modules/@legendapp/spark-global-shortcuts/macos/RNDesktopGlobalShortcuts.mm +7 -4
  276. package/vendor/node_modules/@legendapp/spark-global-shortcuts/package.json +4 -3
  277. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/NativeDesktopGlobalShortcuts.ts +1 -1
  278. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/index.ts +39 -10
  279. package/vendor/node_modules/@legendapp/spark-global-shortcuts/windows/SparkGlobalShortcuts/SparkGlobalShortcuts.h +5 -4
  280. package/vendor/node_modules/@legendapp/spark-message-dialog/macos/RNDesktopMessageDialog.mm +2 -2
  281. package/vendor/node_modules/@legendapp/spark-message-dialog/package.json +2 -2
  282. package/vendor/node_modules/@legendapp/spark-message-dialog/src/NativeDesktopMessageDialog.ts +1 -1
  283. package/vendor/node_modules/@legendapp/spark-message-dialog/src/index.ts +48 -11
  284. package/vendor/node_modules/@legendapp/spark-native-menu/ios/RNNativeMenu.mm +141 -609
  285. package/vendor/node_modules/@legendapp/spark-native-menu/package.json +9 -4
  286. package/vendor/node_modules/@legendapp/spark-native-menu/src/NativeMenu.ts +2 -7
  287. package/vendor/node_modules/@legendapp/spark-native-menu/src/api.ts +76 -121
  288. package/vendor/node_modules/@legendapp/spark-native-menu/src/hooks.ts +97 -0
  289. package/vendor/node_modules/@legendapp/spark-native-menu/src/windows-menus.ts +43 -37
  290. package/vendor/node_modules/@legendapp/spark-notifications/macos/RNDesktopNotifications.mm +75 -30
  291. package/vendor/node_modules/@legendapp/spark-notifications/package.json +2 -2
  292. package/vendor/node_modules/@legendapp/spark-notifications/src/NativeDesktopNotifications.ts +1 -1
  293. package/vendor/node_modules/@legendapp/spark-notifications/src/index.ts +130 -42
  294. package/vendor/node_modules/@legendapp/spark-notifications/windows/SparkNotifications/SparkNotifications.h +46 -8
  295. package/vendor/node_modules/@legendapp/spark-processes/macos/RNDesktopProcesses.mm +94 -21
  296. package/vendor/node_modules/@legendapp/spark-processes/package.json +4 -3
  297. package/vendor/node_modules/@legendapp/spark-processes/src/NativeDesktopProcesses.ts +1 -1
  298. package/vendor/node_modules/@legendapp/spark-processes/src/index.ts +124 -28
  299. package/vendor/node_modules/@legendapp/spark-processes/src/types.ts +34 -0
  300. package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.h +83 -34
  301. package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.vcxproj +2 -0
  302. package/vendor/node_modules/@legendapp/spark-secure-storage/macos/RNDesktopSecureStorage.mm +49 -40
  303. package/vendor/node_modules/@legendapp/spark-secure-storage/package.json +2 -2
  304. package/vendor/node_modules/@legendapp/spark-secure-storage/src/NativeDesktopSecureStorage.ts +1 -1
  305. package/vendor/node_modules/@legendapp/spark-secure-storage/src/desktop.ts +16 -14
  306. package/vendor/node_modules/@legendapp/spark-secure-storage/src/expo.ts +0 -1
  307. package/vendor/node_modules/@legendapp/spark-secure-storage/src/options.ts +3 -2
  308. package/vendor/node_modules/@legendapp/spark-secure-storage/src/unavailable.ts +6 -3
  309. package/vendor/node_modules/@legendapp/spark-settings/package.json +10 -3
  310. package/vendor/node_modules/@legendapp/spark-settings/src/fileStorage.ts +11 -0
  311. package/vendor/node_modules/@legendapp/spark-settings/src/index.ts +7 -9
  312. package/vendor/node_modules/@legendapp/spark-settings/src/storage/index.ts +142 -0
  313. package/vendor/node_modules/@legendapp/spark-settings/src/store.ts +63 -21
  314. package/vendor/node_modules/@legendapp/spark-settings-ui/package.json +20 -0
  315. package/vendor/node_modules/@legendapp/spark-settings-ui/src/index.tsx +436 -0
  316. package/vendor/node_modules/@legendapp/spark-settings-ui/src/options.ts +10 -0
  317. package/vendor/node_modules/@legendapp/spark-sqlite/package.json +3 -3
  318. package/vendor/node_modules/@legendapp/spark-sqlite/src/database.ts +182 -0
  319. package/vendor/node_modules/@legendapp/spark-sqlite/src/index.ts +19 -6
  320. package/vendor/node_modules/@legendapp/spark-sqlite/src/types.ts +22 -0
  321. package/vendor/node_modules/@legendapp/spark-system/macos/RNDesktopSystem.mm +36 -11
  322. package/vendor/node_modules/@legendapp/spark-system/package.json +2 -2
  323. package/vendor/node_modules/@legendapp/spark-system/src/NativeDesktopSystem.ts +1 -1
  324. package/vendor/node_modules/@legendapp/spark-system/src/index.ts +89 -32
  325. package/vendor/node_modules/@legendapp/spark-system/windows/SparkSystem/SparkSystem.h +8 -4
  326. package/vendor/node_modules/@legendapp/spark-tray/macos/RNDesktopTray.mm +20 -8
  327. package/vendor/node_modules/@legendapp/spark-tray/package.json +2 -2
  328. package/vendor/node_modules/@legendapp/spark-tray/src/NativeDesktopTray.ts +1 -1
  329. package/vendor/node_modules/@legendapp/spark-tray/src/index.ts +91 -34
  330. package/vendor/node_modules/@legendapp/spark-tray/windows/SparkTray/SparkTray.h +45 -15
  331. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkButton.mm +2 -1
  332. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkTextInput.mm +10 -2
  333. package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.h +15 -0
  334. package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.mm +1182 -0
  335. package/vendor/node_modules/@legendapp/spark-ui/macos/glass-effect-view/RNGlassEffectView.mm +72 -0
  336. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.h +15 -0
  337. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.mm +194 -0
  338. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.h +15 -0
  339. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.mm +180 -0
  340. package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.h +15 -0
  341. package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.mm +155 -0
  342. package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.h +18 -0
  343. package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.mm +645 -0
  344. package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.h +15 -0
  345. package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.mm +164 -0
  346. package/vendor/node_modules/@legendapp/spark-ui/package.json +16 -4
  347. package/vendor/node_modules/@legendapp/spark-ui/src/SparkSelectNativeComponent.ts +3 -1
  348. package/vendor/node_modules/@legendapp/spark-ui/src/SparkTextInputNativeComponent.ts +6 -2
  349. package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/SidebarSplitViewNativeComponent.ts +31 -0
  350. package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/index.tsx +77 -0
  351. package/vendor/node_modules/@legendapp/spark-ui/src/availability.ts +10 -0
  352. package/vendor/node_modules/@legendapp/spark-ui/src/classnames/index.ts +8 -0
  353. package/vendor/node_modules/@legendapp/spark-ui/src/control.ts +42 -0
  354. package/vendor/node_modules/@legendapp/spark-ui/src/desktop.tsx +56 -0
  355. package/vendor/node_modules/@legendapp/spark-ui/src/glass-effect-view/index.tsx +15 -0
  356. package/vendor/node_modules/@legendapp/spark-ui/src/index.android.tsx +35 -21
  357. package/vendor/node_modules/@legendapp/spark-ui/src/index.ios.tsx +55 -26
  358. package/vendor/node_modules/@legendapp/spark-ui/src/index.tsx +1 -27
  359. package/vendor/node_modules/@legendapp/spark-ui/src/index.web.tsx +45 -14
  360. package/vendor/node_modules/@legendapp/spark-ui/src/index.windows.tsx +1 -42
  361. package/vendor/node_modules/@legendapp/spark-ui/src/mobile-input.tsx +11 -0
  362. package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSegmentedControlNativeComponent.ts +17 -0
  363. package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSelectNativeComponent.ts +17 -0
  364. package/vendor/node_modules/@legendapp/spark-ui/src/select.ts +5 -3
  365. package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/SFSymbolNativeComponent.ts +13 -0
  366. package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/index.tsx +27 -0
  367. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarItemNativeComponent.ts +25 -0
  368. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarNativeComponent.ts +24 -0
  369. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/index.tsx +104 -0
  370. package/vendor/node_modules/@legendapp/spark-ui/src/specialized.ts +22 -0
  371. package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/TextInputSearchNativeComponent.ts +33 -0
  372. package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/index.tsx +34 -0
  373. package/vendor/node_modules/@legendapp/spark-ui/src/types.ts +30 -19
  374. package/vendor/node_modules/@legendapp/spark-ui/uniwind.ts +3 -1
  375. package/vendor/node_modules/@legendapp/spark-ui/uniwind.web.ts +3 -1
  376. package/vendor/node_modules/@legendapp/spark-ui/windows/SparkUI/SparkUI.h +16 -11
  377. package/vendor/node_modules/@legendapp/spark-updates/macos/RNDesktopUpdates.mm +21 -8
  378. package/vendor/node_modules/@legendapp/spark-updates/package.json +2 -2
  379. package/vendor/node_modules/@legendapp/spark-updates/src/NativeDesktopUpdates.ts +3 -2
  380. package/vendor/node_modules/@legendapp/spark-updates/src/index.ts +71 -15
  381. package/vendor/node_modules/@legendapp/spark-webview/package.json +2 -2
  382. package/vendor/node_modules/@legendapp/spark-webview/src/WebView.tsx +11 -0
  383. package/vendor/node_modules/@legendapp/spark-webview/src/adapter.ts +55 -0
  384. package/vendor/node_modules/@legendapp/spark-webview/src/index.ts +2 -2
  385. package/vendor/node_modules/@legendapp/spark-webview/src/types.ts +32 -0
  386. package/vendor/node_modules/@legendapp/spark-webview/src/view-props.ts +28 -0
  387. package/vendor/node_modules/@legendapp/spark-window-options/index.cjs +51 -14
  388. package/vendor/node_modules/@legendapp/spark-window-options/index.d.ts +21 -8
  389. package/vendor/node_modules/@legendapp/spark-window-options/package.json +1 -1
  390. package/windows-macos.ts +1 -0
  391. package/expo-metro.cjs +0 -1
  392. package/message-dialog.ts +0 -1
  393. package/universal.cjs +0 -1
  394. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/accelerator.ts +0 -22
  395. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows-options.ts +0 -11
  396. package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.windows.ts +0 -15
  397. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/accelerator.ts +0 -22
  398. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.h +0 -4
  399. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.mm +0 -53
@@ -0,0 +1,1529 @@
1
+ #if !defined(SPARK_CODEX_TESTING)
2
+ #include "HybridCodexAppServer.hpp"
3
+ #endif
4
+
5
+ #import <Foundation/Foundation.h>
6
+
7
+ #include <algorithm>
8
+ #include <cerrno>
9
+ #include <chrono>
10
+ #include <cmath>
11
+ #include <cctype>
12
+ #include <condition_variable>
13
+ #include <csignal>
14
+ #include <cstdint>
15
+ #include <cstdlib>
16
+ #include <cstring>
17
+ #include <deque>
18
+ #include <fcntl.h>
19
+ #include <map>
20
+ #include <mutex>
21
+ #include <optional>
22
+ #include <poll.h>
23
+ #include <set>
24
+ #include <spawn.h>
25
+ #include <stdexcept>
26
+ #include <string>
27
+ #include <sys/types.h>
28
+ #include <sys/wait.h>
29
+ #include <thread>
30
+ #include <unistd.h>
31
+ #include <vector>
32
+
33
+ extern char** environ;
34
+
35
+ namespace margelo::nitro::legendapps::codex {
36
+
37
+ #if defined(SPARK_CODEX_TESTING)
38
+ struct CodexAvailability {
39
+ bool available; std::string codexPath; std::string message; std::string userAgent;
40
+ CodexAvailability(bool available, std::string codexPath, std::string message, std::string userAgent)
41
+ : available(available), codexPath(std::move(codexPath)), message(std::move(message)), userAgent(std::move(userAgent)) {}
42
+ };
43
+ struct CodexRunResult {
44
+ std::string model; std::string output; std::string threadId; std::string turnId; std::string userAgent;
45
+ CodexRunResult(std::string model, std::string output, std::string threadId, std::string turnId, std::string userAgent)
46
+ : model(std::move(model)), output(std::move(output)), threadId(std::move(threadId)), turnId(std::move(turnId)), userAgent(std::move(userAgent)) {}
47
+ };
48
+ #endif
49
+
50
+ namespace {
51
+
52
+ constexpr size_t kMaximumMessageBytes = 16 * 1024 * 1024;
53
+ constexpr size_t kMaximumStderrBytes = 16 * 1024;
54
+ constexpr double kInitializeTimeoutMs = 15'000;
55
+ constexpr double kMaximumRunTimeoutMs = 86'400'000;
56
+ constexpr size_t kMaximumPreAckBytes = kMaximumMessageBytes;
57
+
58
+ using Clock = std::chrono::steady_clock;
59
+
60
+ std::string toString(NSString* value) {
61
+ if (value == nil) {
62
+ return {};
63
+ }
64
+ const char* utf8 = value.UTF8String;
65
+ return utf8 == nullptr ? std::string() : std::string(utf8);
66
+ }
67
+
68
+ NSString* toNSString(const std::string& value) {
69
+ return [[NSString alloc] initWithBytes:value.data()
70
+ length:value.size()
71
+ encoding:NSUTF8StringEncoding] ?: @"";
72
+ }
73
+
74
+ NSDictionary* parseJsonObject(const std::string& json, const std::string& context) {
75
+ NSData* data = [NSData dataWithBytes:json.data() length:json.size()];
76
+ NSError* error = nil;
77
+ id value = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
78
+ if (![value isKindOfClass:[NSDictionary class]]) {
79
+ std::string detail = error == nil ? "expected a JSON object" : toString(error.localizedDescription);
80
+ throw std::runtime_error(context + ": " + detail);
81
+ }
82
+ return (NSDictionary*)value;
83
+ }
84
+
85
+ std::string serializeJson(NSDictionary* value) {
86
+ NSError* error = nil;
87
+ NSData* data = [NSJSONSerialization dataWithJSONObject:value options:0 error:&error];
88
+ if (data == nil) {
89
+ throw std::runtime_error("Could not encode a Codex app-server request: " + toString(error.localizedDescription));
90
+ }
91
+ return std::string(static_cast<const char*>(data.bytes), data.length);
92
+ }
93
+
94
+ NSDictionary* dictionaryValue(id value) {
95
+ return [value isKindOfClass:[NSDictionary class]] ? (NSDictionary*)value : nil;
96
+ }
97
+
98
+ NSArray* arrayValue(id value) {
99
+ return [value isKindOfClass:[NSArray class]] ? (NSArray*)value : nil;
100
+ }
101
+
102
+ NSString* stringValue(id value) {
103
+ return [value isKindOfClass:[NSString class]] ? (NSString*)value : nil;
104
+ }
105
+
106
+ bool boolValue(id value) {
107
+ return [value respondsToSelector:@selector(boolValue)] && [value boolValue];
108
+ }
109
+
110
+ std::string responseError(NSDictionary* response) {
111
+ NSDictionary* error = dictionaryValue(response[@"error"]);
112
+ if (error == nil) {
113
+ return {};
114
+ }
115
+ NSString* message = stringValue(error[@"message"]);
116
+ if (message != nil) {
117
+ return toString(message);
118
+ }
119
+ return toString([error description]);
120
+ }
121
+
122
+ bool containsInsensitive(const std::string& value, const std::string& needle) {
123
+ std::string lowerValue(value);
124
+ std::string lowerNeedle(needle);
125
+ std::transform(lowerValue.begin(), lowerValue.end(), lowerValue.begin(), ::tolower);
126
+ std::transform(lowerNeedle.begin(), lowerNeedle.end(), lowerNeedle.begin(), ::tolower);
127
+ return lowerValue.find(lowerNeedle) != std::string::npos;
128
+ }
129
+
130
+ std::string actionableError(const std::string& message) {
131
+ if (message.empty()) {
132
+ return "Codex failed without an error message. Run `codex login` in Terminal, then try again.";
133
+ }
134
+ if (containsInsensitive(message, "auth") || containsInsensitive(message, "login") ||
135
+ containsInsensitive(message, "unauthorized") || containsInsensitive(message, "401")) {
136
+ if (containsInsensitive(message, "codex login")) {
137
+ return message;
138
+ }
139
+ return message + " Run `codex login` in Terminal, then try again.";
140
+ }
141
+ if (containsInsensitive(message, "rate limit") || containsInsensitive(message, "usage limit") ||
142
+ containsInsensitive(message, "quota")) {
143
+ return message + " Wait for your Codex usage limit to reset, or select another model in Codex.";
144
+ }
145
+ if (containsInsensitive(message, "method not found") || containsInsensitive(message, "invalid params") ||
146
+ containsInsensitive(message, "protocol")) {
147
+ return message + " Update Codex to a version that supports app-server, then reopen the app.";
148
+ }
149
+ return message;
150
+ }
151
+
152
+ std::string resolveCodexPath() {
153
+ @autoreleasepool {
154
+ NSFileManager* fileManager = [NSFileManager defaultManager];
155
+ NSMutableArray<NSString*>* candidates = [NSMutableArray array];
156
+ NSString* explicitPath = NSProcessInfo.processInfo.environment[@"CODEX_PATH"];
157
+ if (explicitPath.length > 0) {
158
+ [candidates addObject:[explicitPath stringByExpandingTildeInPath]];
159
+ }
160
+
161
+ NSString* path = NSProcessInfo.processInfo.environment[@"PATH"];
162
+ for (NSString* directory in [path componentsSeparatedByString:@":"]) {
163
+ if (directory.length > 0) {
164
+ [candidates addObject:[directory stringByAppendingPathComponent:@"codex"]];
165
+ }
166
+ }
167
+
168
+ NSString* home = NSHomeDirectory();
169
+ [candidates addObjectsFromArray:@[
170
+ [home stringByAppendingPathComponent:@".local/bin/codex"],
171
+ [home stringByAppendingPathComponent:@".bun/bin/codex"],
172
+ [home stringByAppendingPathComponent:@"bin/codex"],
173
+ @"/opt/homebrew/bin/codex",
174
+ @"/usr/local/bin/codex",
175
+ @"/usr/bin/codex",
176
+ ]];
177
+
178
+ NSMutableSet<NSString*>* visited = [NSMutableSet set];
179
+ for (NSString* candidate in candidates) {
180
+ NSString* standardized = candidate.stringByStandardizingPath;
181
+ if ([visited containsObject:standardized]) {
182
+ continue;
183
+ }
184
+ [visited addObject:standardized];
185
+ BOOL isDirectory = NO;
186
+ if ([fileManager fileExistsAtPath:standardized isDirectory:&isDirectory] && !isDirectory &&
187
+ [fileManager isExecutableFileAtPath:standardized]) {
188
+ return toString(standardized);
189
+ }
190
+ }
191
+ return {};
192
+ }
193
+ }
194
+
195
+ NSString* defaultWorkingDirectory();
196
+
197
+ NSString* isolatedCodexHome() {
198
+ NSString* bundleIdentifier = NSBundle.mainBundle.bundleIdentifier ?: @"legend-apps";
199
+ NSString* path = [defaultWorkingDirectory()
200
+ stringByAppendingPathComponent:[@"codex-home-" stringByAppendingString:bundleIdentifier]];
201
+ NSFileManager* fileManager = NSFileManager.defaultManager;
202
+ [fileManager createDirectoryAtPath:path
203
+ withIntermediateDirectories:YES
204
+ attributes:nil
205
+ error:nil];
206
+ [fileManager setAttributes:@{NSFilePosixPermissions: @(0700)} ofItemAtPath:path error:nil];
207
+
208
+ NSString* sourceCodexHome = NSProcessInfo.processInfo.environment[@"CODEX_HOME"];
209
+ if (sourceCodexHome.length == 0) {
210
+ sourceCodexHome = [NSHomeDirectory() stringByAppendingPathComponent:@".codex"];
211
+ }
212
+ NSString* sourceAuthPath = [sourceCodexHome stringByAppendingPathComponent:@"auth.json"];
213
+ NSString* targetAuthPath = [path stringByAppendingPathComponent:@"auth.json"];
214
+ if ([fileManager fileExistsAtPath:sourceAuthPath] && ![fileManager fileExistsAtPath:targetAuthPath]) {
215
+ [fileManager createSymbolicLinkAtPath:targetAuthPath withDestinationPath:sourceAuthPath error:nil];
216
+ }
217
+ return path;
218
+ }
219
+
220
+ std::vector<std::string> sanitizedEnvironmentStorage() {
221
+ static const std::set<std::string> excluded = {
222
+ "CODEX_CI",
223
+ "CODEX_CLI_AUTH_CREDENTIALS_STORE",
224
+ "CODEX_HOME",
225
+ "CODEX_INTERNAL_ORIGINATOR_OVERRIDE",
226
+ "CODEX_SESSION_ID",
227
+ "CODEX_SHELL",
228
+ "CODEX_THREAD_ID",
229
+ };
230
+ std::vector<std::string> values;
231
+ for (char** entry = environ; entry != nullptr && *entry != nullptr; ++entry) {
232
+ std::string value(*entry);
233
+ const size_t separator = value.find('=');
234
+ const std::string key = value.substr(0, separator);
235
+ if (!excluded.contains(key)) {
236
+ values.push_back(std::move(value));
237
+ }
238
+ }
239
+ values.push_back("CODEX_CLI_AUTH_CREDENTIALS_STORE=file");
240
+ values.push_back("CODEX_HOME=" + toString(isolatedCodexHome()));
241
+ return values;
242
+ }
243
+
244
+ NSMutableDictionary<NSString*, NSString*>* sanitizedEnvironmentDictionary() {
245
+ NSMutableDictionary<NSString*, NSString*>* environment =
246
+ [NSProcessInfo.processInfo.environment mutableCopy];
247
+ for (NSString* key in @[
248
+ @"CODEX_CI",
249
+ @"CODEX_INTERNAL_ORIGINATOR_OVERRIDE",
250
+ @"CODEX_SESSION_ID",
251
+ @"CODEX_SHELL",
252
+ @"CODEX_THREAD_ID",
253
+ ]) {
254
+ [environment removeObjectForKey:key];
255
+ }
256
+ environment[@"CODEX_CLI_AUTH_CREDENTIALS_STORE"] = @"file";
257
+ environment[@"CODEX_HOME"] = isolatedCodexHome();
258
+ return environment;
259
+ }
260
+
261
+ NSString* defaultWorkingDirectory() {
262
+ NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"legend-codex-workspace"];
263
+ NSError* error = nil;
264
+ if ([[NSFileManager defaultManager] createDirectoryAtPath:path
265
+ withIntermediateDirectories:YES
266
+ attributes:nil
267
+ error:&error]) {
268
+ return path;
269
+ }
270
+ return NSTemporaryDirectory();
271
+ }
272
+
273
+ std::vector<char*> pointerArray(std::vector<std::string>& values) {
274
+ std::vector<char*> pointers;
275
+ pointers.reserve(values.size() + 1);
276
+ for (std::string& value : values) {
277
+ pointers.push_back(value.data());
278
+ }
279
+ pointers.push_back(nullptr);
280
+ return pointers;
281
+ }
282
+
283
+ struct TurnState {
284
+ std::string error;
285
+ std::string output;
286
+ std::string threadId;
287
+ bool done = false;
288
+ bool acknowledged = false;
289
+ uint64_t sequence = 0;
290
+ size_t preAckBytes = 0;
291
+ bool preAckLimitExceeded = false;
292
+ };
293
+
294
+ class CodexProcessSupervisor {
295
+ public:
296
+ static CodexProcessSupervisor& shared() {
297
+ static CodexProcessSupervisor* supervisor = new CodexProcessSupervisor();
298
+ return *supervisor;
299
+ }
300
+
301
+ CodexAvailability getAvailability() {
302
+ @autoreleasepool {
303
+ const std::string path = resolveCodexPath();
304
+ if (path.empty()) {
305
+ return CodexAvailability(
306
+ false,
307
+ "",
308
+ "Codex CLI was not found. Install Codex, or set CODEX_PATH to the Codex executable, then reopen the app.",
309
+ "");
310
+ }
311
+ {
312
+ std::lock_guard lock(mutex_);
313
+ if (useExecFallback_) {
314
+ return CodexAvailability(
315
+ true,
316
+ path,
317
+ "Codex is ready in compatibility mode.",
318
+ userAgent_);
319
+ }
320
+ }
321
+ try {
322
+ ensureInitialized();
323
+ std::lock_guard lock(mutex_);
324
+ return CodexAvailability(true, codexPath_, "Codex is ready.", userAgent_);
325
+ } catch (const std::exception& error) {
326
+ return CodexAvailability(false, path, actionableError(error.what()), "");
327
+ }
328
+ }
329
+ }
330
+
331
+ CodexRunResult runPrompt(
332
+ const std::string& prompt,
333
+ const std::string& cwd,
334
+ const std::string& reasoningEffort,
335
+ double timeoutMs,
336
+ const std::string& outputSchemaJson,
337
+ const std::string& developerInstructions) {
338
+ @autoreleasepool {
339
+ if (prompt.empty()) {
340
+ throw std::runtime_error("Enter a prompt before running Codex.");
341
+ }
342
+ if (!std::isfinite(timeoutMs) || std::floor(timeoutMs) != timeoutMs || timeoutMs < 1'000 || timeoutMs > kMaximumRunTimeoutMs) {
343
+ throw std::runtime_error("Codex timeout must be a whole number from 1000 to 86400000 milliseconds.");
344
+ }
345
+
346
+ uint64_t acceptedCancellationGeneration = 0;
347
+ {
348
+ std::lock_guard lock(mutex_);
349
+ acceptedCancellationGeneration = cancelGeneration_;
350
+ ++activeRuns_;
351
+ }
352
+ struct RunGuard {
353
+ CodexProcessSupervisor* owner;
354
+ ~RunGuard() { std::lock_guard lock(owner->mutex_); --owner->activeRuns_; owner->condition_.notify_all(); }
355
+ } runGuard{this};
356
+ auto throwIfCancelled = [&]() {
357
+ std::lock_guard lock(mutex_);
358
+ if (cancelGeneration_ != acceptedCancellationGeneration) throw std::runtime_error("Codex request was cancelled.");
359
+ };
360
+
361
+ const auto deadline = Clock::now() + std::chrono::milliseconds(static_cast<int64_t>(timeoutMs));
362
+ NSString* requestedCwd = cwd.empty() ? defaultWorkingDirectory() : toNSString(cwd);
363
+ BOOL isDirectory = NO;
364
+ if (![[NSFileManager defaultManager] fileExistsAtPath:requestedCwd isDirectory:&isDirectory] || !isDirectory) {
365
+ throw std::runtime_error("Codex working directory does not exist: " + toString(requestedCwd));
366
+ }
367
+
368
+ bool useExecFallback = false;
369
+ {
370
+ std::lock_guard lock(mutex_);
371
+ useExecFallback = useExecFallback_;
372
+ }
373
+ if (useExecFallback) {
374
+ throwIfCancelled();
375
+ return runExecFallback(
376
+ prompt,
377
+ requestedCwd,
378
+ reasoningEffort,
379
+ outputSchemaJson,
380
+ developerInstructions,
381
+ acceptedCancellationGeneration,
382
+ std::min(deadline, Clock::now() + std::chrono::seconds(20)));
383
+ }
384
+
385
+ try { ensureInitialized(acceptedCancellationGeneration, deadline); } catch (...) { throwIfCancelled(); throw; }
386
+ throwIfCancelled();
387
+ uint64_t runProcessGeneration = 0;
388
+ { std::lock_guard lock(mutex_); runProcessGeneration = processGeneration_; }
389
+ if (runProcessGeneration == 0) throw std::runtime_error("Codex app-server is not running.");
390
+
391
+ NSMutableDictionary* threadParams = [@{
392
+ @"approvalPolicy": @"never",
393
+ @"config": @{
394
+ @"cli_auth_credentials_store": @"file",
395
+ @"features": @{
396
+ @"apps": @NO,
397
+ @"plugins": @NO,
398
+ @"remote_plugin": @NO,
399
+ @"shell_snapshot": @NO,
400
+ @"shell_tool": @NO,
401
+ },
402
+ @"mcp_servers": @{},
403
+ @"orchestrator": @{
404
+ @"mcp": @{
405
+ @"enabled": @NO,
406
+ },
407
+ },
408
+ },
409
+ @"cwd": requestedCwd,
410
+ @"ephemeral": @YES,
411
+ @"sandbox": @"read-only",
412
+ } mutableCopy];
413
+ if (!developerInstructions.empty()) {
414
+ threadParams[@"developerInstructions"] = toNSString(developerInstructions);
415
+ }
416
+
417
+ NSDictionary* threadResponse = nil;
418
+ try {
419
+ const auto threadDeadline = std::min(deadline, Clock::now() + std::chrono::seconds(5));
420
+ threadResponse = request(@"thread/start", threadParams, threadDeadline, acceptedCancellationGeneration, runProcessGeneration);
421
+ } catch (const std::exception& error) {
422
+ throwIfCancelled();
423
+ if (!containsInsensitive(error.what(), "did not respond in time")) {
424
+ throw;
425
+ }
426
+ {
427
+ std::lock_guard lock(mutex_);
428
+ if (processGeneration_ != runProcessGeneration) throw std::runtime_error("Codex app-server restarted during thread startup.");
429
+ useExecFallback_ = true;
430
+ }
431
+ terminateProcessGeneration(runProcessGeneration, "Codex app-server did not respond during thread startup.");
432
+ return runExecFallback(
433
+ prompt,
434
+ requestedCwd,
435
+ reasoningEffort,
436
+ outputSchemaJson,
437
+ developerInstructions,
438
+ acceptedCancellationGeneration,
439
+ std::min(deadline, Clock::now() + std::chrono::seconds(20)));
440
+ }
441
+ throwIfCancelled();
442
+ throwForResponseError(threadResponse, "Codex could not start a thread");
443
+ NSDictionary* threadResult = dictionaryValue(threadResponse[@"result"]);
444
+ NSDictionary* thread = dictionaryValue(threadResult[@"thread"]);
445
+ std::string threadId = toString(stringValue(thread[@"id"]));
446
+ std::string model = toString(stringValue(threadResult[@"model"]));
447
+ if (threadId.empty()) {
448
+ throw std::runtime_error(
449
+ "Codex app-server returned an invalid thread response. Update Codex, then reopen the app.");
450
+ }
451
+
452
+ NSMutableDictionary* turnParams = [@{
453
+ @"effort": toNSString(reasoningEffort),
454
+ @"input": @[@{
455
+ @"text": toNSString(prompt),
456
+ @"text_elements": @[],
457
+ @"type": @"text",
458
+ }],
459
+ @"threadId": toNSString(threadId),
460
+ } mutableCopy];
461
+ if (!outputSchemaJson.empty()) {
462
+ NSDictionary* schema = parseJsonObject(outputSchemaJson, "Invalid Codex output schema");
463
+ turnParams[@"outputSchema"] = schema;
464
+ }
465
+
466
+ {
467
+ std::lock_guard lock(mutex_);
468
+ if (processGeneration_ != runProcessGeneration) throw std::runtime_error("Codex app-server restarted before turn startup.");
469
+ ++pendingTurnStarts_;
470
+ }
471
+ bool pendingTurnStart = true;
472
+ auto finishPendingTurnStart = [&]() {
473
+ if (!pendingTurnStart) return;
474
+ std::lock_guard lock(mutex_);
475
+ if (processGeneration_ == runProcessGeneration && pendingTurnStarts_ > 0) --pendingTurnStarts_;
476
+ pendingTurnStart = false;
477
+ };
478
+ std::string turnId;
479
+ uint64_t cancelGeneration = acceptedCancellationGeneration;
480
+ try {
481
+ NSDictionary* turnResponse = request(@"turn/start", turnParams, deadline, acceptedCancellationGeneration, runProcessGeneration);
482
+ #if defined(SPARK_CODEX_TESTING)
483
+ if (const char* delay = std::getenv("CODEX_TEST_DELAY_AFTER_TURN_RESPONSE_MS")) {
484
+ const int milliseconds = std::max(0, std::min(1000, std::atoi(delay)));
485
+ std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
486
+ }
487
+ #endif
488
+ throwIfCancelled();
489
+ throwForResponseError(turnResponse, "Codex could not start a turn");
490
+ NSDictionary* turnResult = dictionaryValue(turnResponse[@"result"]);
491
+ NSDictionary* turn = dictionaryValue(turnResult[@"turn"]);
492
+ turnId = toString(stringValue(turn[@"id"]));
493
+ if (turnId.empty()) {
494
+ throw std::runtime_error(
495
+ "Codex app-server returned an invalid turn response. Update Codex, then reopen the app.");
496
+ }
497
+ std::lock_guard lock(mutex_);
498
+ if (processGeneration_ != runProcessGeneration) throw std::runtime_error("Codex app-server restarted before the turn was accepted.");
499
+ TurnState& state = turns_[turnId];
500
+ state.threadId = threadId;
501
+ preAckBytes_ -= state.preAckBytes;
502
+ state.preAckBytes = 0;
503
+ state.acknowledged = true;
504
+ --pendingTurnStarts_;
505
+ pendingTurnStart = false;
506
+ } catch (...) {
507
+ finishPendingTurnStart();
508
+ throwIfCancelled();
509
+ throw;
510
+ }
511
+
512
+ TurnState completed;
513
+ {
514
+ std::unique_lock lock(mutex_);
515
+ const bool finished = condition_.wait_until(lock, deadline, [&]() {
516
+ const auto state = turns_.find(turnId);
517
+ return processGeneration_ != runProcessGeneration || cancelGeneration_ != cancelGeneration ||
518
+ (state != turns_.end() && state->second.done);
519
+ });
520
+ auto state = turns_.find(turnId);
521
+ if (cancelGeneration_ != cancelGeneration) {
522
+ throw std::runtime_error("Codex request was cancelled.");
523
+ } else if (processGeneration_ != runProcessGeneration) {
524
+ const std::string detail = processError_.empty() ? "Codex app-server restarted while the request was running." : processError_;
525
+ throw std::runtime_error(actionableError(detail));
526
+ } else if (finished && state != turns_.end() && state->second.done) {
527
+ completed = state->second;
528
+ turns_.erase(state);
529
+ retireTurnLocked(turnId);
530
+ } else {
531
+ turns_.erase(turnId);
532
+ retireTurnLocked(turnId);
533
+ lock.unlock();
534
+ interruptTurn(threadId, turnId, runProcessGeneration);
535
+ const int seconds = static_cast<int>(timeoutMs / 1000.0);
536
+ throw std::runtime_error(
537
+ "Codex did not finish within " + std::to_string(seconds) +
538
+ " seconds. Try a shorter prompt; if Codex is also stuck in Terminal, run `codex login`.");
539
+ }
540
+ }
541
+
542
+ if (!completed.error.empty()) {
543
+ throw std::runtime_error(actionableError(completed.error));
544
+ }
545
+ if (completed.preAckLimitExceeded) {
546
+ throw std::runtime_error("Codex app-server response exceeded the 16 MiB pre-ack notification limit.");
547
+ }
548
+ if (completed.output.empty()) {
549
+ throw std::runtime_error(
550
+ "Codex completed without a response. Try again; if it keeps happening, update Codex and reopen the app.");
551
+ }
552
+
553
+ std::lock_guard lock(mutex_);
554
+ return CodexRunResult(model, completed.output, threadId, turnId, userAgent_);
555
+ }
556
+ }
557
+
558
+ double cancelActiveRuns() {
559
+ pid_t serverPid = -1;
560
+ uint64_t serverGeneration = 0;
561
+ size_t acceptedRuns = 0;
562
+ std::vector<pid_t> fallbackPids;
563
+ {
564
+ std::lock_guard lock(mutex_);
565
+ ++cancelGeneration_;
566
+ acceptedRuns = activeRuns_;
567
+ serverPid = activeRuns_ > 0 ? pid_ : -1;
568
+ serverGeneration = activeRuns_ > 0 ? processGeneration_ : 0;
569
+ fallbackPids.assign(fallbackPids_.begin(), fallbackPids_.end());
570
+ condition_.notify_all();
571
+ }
572
+ if (serverGeneration > 0) terminateProcessGeneration(serverGeneration, "Codex request was cancelled.");
573
+ else if (serverPid > 0) kill(-serverPid, SIGTERM);
574
+ for (pid_t pid : fallbackPids) {
575
+ kill(pid, SIGTERM);
576
+ }
577
+ return static_cast<double>(acceptedRuns);
578
+ }
579
+
580
+ double shutdown() {
581
+ std::lock_guard lifecycleLock(lifecycleMutex_);
582
+ pid_t pid = -1;
583
+ int stdinFd = -1;
584
+ std::vector<pid_t> fallbackPids;
585
+ {
586
+ std::lock_guard lock(mutex_);
587
+ ++cancelGeneration_;
588
+ pid = pid_;
589
+ stdinFd = stdinFd_;
590
+ pid_ = -1;
591
+ stdinFd_ = -1;
592
+ initialized_ = false;
593
+ processGeneration_ = 0;
594
+ processError_ = "Codex app-server was stopped.";
595
+ responses_.clear();
596
+ ignoredResponseIds_.clear();
597
+ fallbackPids.assign(fallbackPids_.begin(), fallbackPids_.end());
598
+ fallbackPids_.clear();
599
+ useExecFallback_ = false;
600
+ for (auto& [turnId, state] : turns_) {
601
+ state.error = processError_;
602
+ state.done = true;
603
+ }
604
+ condition_.notify_all();
605
+ }
606
+ if (stdinFd >= 0) {
607
+ close(stdinFd);
608
+ }
609
+ if (pid > 0) {
610
+ kill(-pid, SIGTERM);
611
+ }
612
+ for (pid_t fallbackPid : fallbackPids) {
613
+ kill(fallbackPid, SIGTERM);
614
+ }
615
+ return static_cast<double>((pid > 0 ? 1 : 0) + fallbackPids.size());
616
+ }
617
+
618
+ #if defined(SPARK_CODEX_TESTING)
619
+ size_t retainedTurnCountForTesting() {
620
+ std::lock_guard lock(mutex_);
621
+ return turns_.size();
622
+ }
623
+ size_t blockedWriteCountForTesting() {
624
+ std::lock_guard lock(mutex_);
625
+ return blockedWriteCount_;
626
+ }
627
+ #endif
628
+
629
+ private:
630
+ void retireTurnLocked(const std::string& turnId) {
631
+ if (turnId.empty() || !retiredTurnIds_.insert(turnId).second) return;
632
+ retiredTurnOrder_.push_back(turnId);
633
+ while (retiredTurnOrder_.size() > 512) {
634
+ retiredTurnIds_.erase(retiredTurnOrder_.front());
635
+ retiredTurnOrder_.pop_front();
636
+ }
637
+ }
638
+
639
+ TurnState* pendingOrActiveTurnLocked(const std::string& turnId) {
640
+ if (turnId.empty() || retiredTurnIds_.contains(turnId)) return nullptr;
641
+ auto found = turns_.find(turnId);
642
+ if (found != turns_.end()) return &found->second;
643
+ if (pendingTurnStarts_ == 0) return nullptr;
644
+ while (turns_.size() >= 128) {
645
+ auto oldest = turns_.end();
646
+ for (auto it = turns_.begin(); it != turns_.end(); ++it) {
647
+ if (!it->second.acknowledged && (oldest == turns_.end() || it->second.sequence < oldest->second.sequence)) oldest = it;
648
+ }
649
+ if (oldest == turns_.end()) return nullptr;
650
+ preAckBytes_ -= oldest->second.preAckBytes;
651
+ turns_.erase(oldest);
652
+ }
653
+ TurnState state;
654
+ state.sequence = ++nextTurnSequence_;
655
+ return &turns_.emplace(turnId, std::move(state)).first->second;
656
+ }
657
+
658
+ void setPreAckTextLocked(TurnState& state, std::string TurnState::*field, const std::string& value) {
659
+ std::string& current = state.*field;
660
+ if (state.acknowledged) { current = value; return; }
661
+ if (state.preAckLimitExceeded) return;
662
+ const size_t nextTotal = preAckBytes_ - current.size() + value.size();
663
+ if (nextTotal > kMaximumPreAckBytes) {
664
+ preAckBytes_ -= state.preAckBytes;
665
+ state.preAckBytes = 0;
666
+ state.output.clear();
667
+ state.error.clear();
668
+ state.preAckLimitExceeded = true;
669
+ return;
670
+ }
671
+ preAckBytes_ = nextTotal;
672
+ state.preAckBytes = state.preAckBytes - current.size() + value.size();
673
+ current = value;
674
+ }
675
+
676
+ CodexRunResult runExecFallback(
677
+ const std::string& prompt,
678
+ NSString* requestedCwd,
679
+ const std::string& reasoningEffort,
680
+ const std::string& outputSchemaJson,
681
+ const std::string& developerInstructions,
682
+ uint64_t acceptedCancellationGeneration,
683
+ Clock::time_point deadline) {
684
+ { std::lock_guard lock(mutex_); if (cancelGeneration_ != acceptedCancellationGeneration) throw std::runtime_error("Codex request was cancelled."); }
685
+ const std::string codexPath = resolveCodexPath();
686
+ if (codexPath.empty()) {
687
+ throw std::runtime_error(
688
+ "Codex CLI was not found. Install Codex, or set CODEX_PATH to the Codex executable, then reopen the app.");
689
+ }
690
+
691
+ NSFileManager* fileManager = [NSFileManager defaultManager];
692
+ NSString* temporaryDirectory = NSTemporaryDirectory();
693
+ NSString* identifier = NSUUID.UUID.UUIDString;
694
+ NSString* promptPath = [temporaryDirectory
695
+ stringByAppendingPathComponent:[NSString stringWithFormat:@"legend-codex-%@-prompt.txt", identifier]];
696
+ NSString* outputPath = [temporaryDirectory
697
+ stringByAppendingPathComponent:[NSString stringWithFormat:@"legend-codex-%@-output.txt", identifier]];
698
+ NSString* errorPath = [temporaryDirectory
699
+ stringByAppendingPathComponent:[NSString stringWithFormat:@"legend-codex-%@-error.txt", identifier]];
700
+ NSString* schemaPath = outputSchemaJson.empty()
701
+ ? nil
702
+ : [temporaryDirectory
703
+ stringByAppendingPathComponent:[NSString stringWithFormat:@"legend-codex-%@-schema.json", identifier]];
704
+ NSArray<NSString*>* temporaryPaths = schemaPath == nil
705
+ ? @[promptPath, outputPath, errorPath]
706
+ : @[promptPath, outputPath, errorPath, schemaPath];
707
+ auto cleanup = [&]() {
708
+ for (NSString* path in temporaryPaths) {
709
+ [fileManager removeItemAtPath:path error:nil];
710
+ }
711
+ };
712
+
713
+ std::string combinedPrompt = prompt;
714
+ if (!developerInstructions.empty()) {
715
+ combinedPrompt = developerInstructions + "\n\nUser request:\n" + prompt;
716
+ }
717
+ NSError* fileError = nil;
718
+ if (![toNSString(combinedPrompt) writeToFile:promptPath
719
+ atomically:YES
720
+ encoding:NSUTF8StringEncoding
721
+ error:&fileError]) {
722
+ cleanup();
723
+ throw std::runtime_error(
724
+ "Could not prepare the Codex request: " + toString(fileError.localizedDescription));
725
+ }
726
+ [fileManager createFileAtPath:outputPath contents:nil attributes:nil];
727
+ [fileManager createFileAtPath:errorPath contents:nil attributes:nil];
728
+
729
+ if (schemaPath != nil) {
730
+ parseJsonObject(outputSchemaJson, "Invalid Codex output schema");
731
+ if (![toNSString(outputSchemaJson) writeToFile:schemaPath
732
+ atomically:YES
733
+ encoding:NSUTF8StringEncoding
734
+ error:&fileError]) {
735
+ cleanup();
736
+ throw std::runtime_error(
737
+ "Could not prepare the Codex output schema: " + toString(fileError.localizedDescription));
738
+ }
739
+ }
740
+
741
+ NSMutableArray<NSString*>* arguments = [NSMutableArray arrayWithArray:@[
742
+ @"--ask-for-approval",
743
+ @"never",
744
+ @"exec",
745
+ @"--ephemeral",
746
+ @"--ignore-user-config",
747
+ @"--ignore-rules",
748
+ @"--disable",
749
+ @"apps",
750
+ @"--disable",
751
+ @"plugins",
752
+ @"--disable",
753
+ @"remote_plugin",
754
+ @"--disable",
755
+ @"shell_snapshot",
756
+ @"--disable",
757
+ @"shell_tool",
758
+ @"--skip-git-repo-check",
759
+ @"--color",
760
+ @"never",
761
+ @"--sandbox",
762
+ @"read-only",
763
+ @"--config",
764
+ [NSString stringWithFormat:@"model_reasoning_effort=\"%@\"", toNSString(reasoningEffort)],
765
+ @"--config",
766
+ @"mcp_servers={}",
767
+ @"--config",
768
+ @"orchestrator.mcp.enabled=false",
769
+ @"--config",
770
+ @"cli_auth_credentials_store=\"file\"",
771
+ @"--output-last-message",
772
+ outputPath,
773
+ ]];
774
+ if (schemaPath != nil) {
775
+ [arguments addObjectsFromArray:@[@"--output-schema", schemaPath]];
776
+ }
777
+ [arguments addObject:@"-"];
778
+
779
+ NSFileHandle* promptHandle = [NSFileHandle fileHandleForReadingAtPath:promptPath];
780
+ NSFileHandle* errorHandle = [NSFileHandle fileHandleForWritingAtPath:errorPath];
781
+ if (promptHandle == nil || errorHandle == nil) {
782
+ cleanup();
783
+ throw std::runtime_error("Could not open temporary files for the Codex request.");
784
+ }
785
+
786
+ NSTask* task = [NSTask new];
787
+ task.executableURL = [NSURL fileURLWithPath:toNSString(codexPath)];
788
+ task.arguments = arguments;
789
+ task.currentDirectoryURL = [NSURL fileURLWithPath:requestedCwd isDirectory:YES];
790
+ task.environment = sanitizedEnvironmentDictionary();
791
+ task.standardInput = promptHandle;
792
+ task.standardOutput = NSFileHandle.fileHandleWithNullDevice;
793
+ task.standardError = errorHandle;
794
+
795
+ NSError* launchError = nil;
796
+ if (![task launchAndReturnError:&launchError]) {
797
+ [promptHandle closeFile];
798
+ [errorHandle closeFile];
799
+ cleanup();
800
+ throw std::runtime_error(
801
+ actionableError("Could not start Codex: " + toString(launchError.localizedDescription)));
802
+ }
803
+
804
+ const pid_t pid = task.processIdentifier;
805
+ {
806
+ std::lock_guard lock(mutex_);
807
+ fallbackPids_.insert(pid);
808
+ if (cancelGeneration_ != acceptedCancellationGeneration) kill(pid, SIGTERM);
809
+ }
810
+ while (task.isRunning && Clock::now() < deadline) {
811
+ { std::lock_guard lock(mutex_); if (cancelGeneration_ != acceptedCancellationGeneration) break; }
812
+ usleep(10'000);
813
+ }
814
+ if (task.isRunning) {
815
+ [task terminate];
816
+ const auto terminationDeadline = Clock::now() + std::chrono::seconds(1);
817
+ while (task.isRunning && Clock::now() < terminationDeadline) {
818
+ usleep(10'000);
819
+ }
820
+ if (task.isRunning) {
821
+ kill(pid, SIGKILL);
822
+ }
823
+ }
824
+ [task waitUntilExit];
825
+ [promptHandle closeFile];
826
+ [errorHandle closeFile];
827
+ {
828
+ std::lock_guard lock(mutex_);
829
+ fallbackPids_.erase(pid);
830
+ }
831
+
832
+ NSString* output = [NSString stringWithContentsOfFile:outputPath
833
+ encoding:NSUTF8StringEncoding
834
+ error:nil] ?: @"";
835
+ NSString* errorOutput = [NSString stringWithContentsOfFile:errorPath
836
+ encoding:NSUTF8StringEncoding
837
+ error:nil] ?: @"";
838
+ cleanup();
839
+
840
+ {
841
+ std::lock_guard lock(mutex_);
842
+ if (cancelGeneration_ != acceptedCancellationGeneration) {
843
+ throw std::runtime_error("Codex request was cancelled.");
844
+ }
845
+ }
846
+ if (Clock::now() >= deadline) {
847
+ throw std::runtime_error(
848
+ "Codex could not start from this app. In Terminal, run `codex login status`, then "
849
+ "`codex exec \"Reply with ok\"`. If both work, update Codex and reopen this app; "
850
+ "your Codex version has a background-process startup issue.");
851
+ }
852
+ if (task.terminationStatus != 0) {
853
+ std::string detail = toString([errorOutput stringByTrimmingCharactersInSet:
854
+ NSCharacterSet.whitespaceAndNewlineCharacterSet]);
855
+ if (detail.empty()) {
856
+ detail = "Codex exited with status " + std::to_string(task.terminationStatus) + ".";
857
+ }
858
+ throw std::runtime_error(actionableError("Codex could not generate suggestions: " + detail));
859
+ }
860
+ std::string result = toString([output stringByTrimmingCharactersInSet:
861
+ NSCharacterSet.whitespaceAndNewlineCharacterSet]);
862
+ if (result.empty()) {
863
+ throw std::runtime_error(
864
+ "Codex completed without a response. Try again; if it keeps happening, update Codex and reopen the app.");
865
+ }
866
+ std::lock_guard lock(mutex_);
867
+ return CodexRunResult("", result, "", "", userAgent_);
868
+ }
869
+
870
+ void ensureInitialized(uint64_t cancellationGeneration = UINT64_MAX, Clock::time_point runDeadline = Clock::time_point::max()) {
871
+ const auto initializeDeadline = std::min(runDeadline, Clock::now() + std::chrono::milliseconds(static_cast<int64_t>(kInitializeTimeoutMs)));
872
+ std::unique_lock lifecycleLock(lifecycleMutex_, std::defer_lock);
873
+ while (!lifecycleLock.try_lock_for(std::chrono::milliseconds(10))) {
874
+ std::lock_guard lock(mutex_);
875
+ if (cancellationGeneration != UINT64_MAX && cancelGeneration_ != cancellationGeneration) throw std::runtime_error("Codex request was cancelled.");
876
+ if (Clock::now() >= initializeDeadline) throw std::runtime_error("Codex app-server did not respond in time.");
877
+ }
878
+ {
879
+ std::lock_guard lock(mutex_);
880
+ if (initialized_ && pid_ > 0) {
881
+ return;
882
+ }
883
+ }
884
+ startProcess();
885
+ try {
886
+ const auto deadline = initializeDeadline;
887
+ NSDictionary* response = request(@"initialize", @{
888
+ @"capabilities": @{
889
+ @"experimentalApi": @NO,
890
+ @"optOutNotificationMethods": @[
891
+ @"account/rateLimits/updated",
892
+ @"item/agentMessage/delta",
893
+ @"item/started",
894
+ @"mcpServer/startupStatus/updated",
895
+ @"thread/started",
896
+ @"thread/status/changed",
897
+ @"thread/tokenUsage/updated",
898
+ @"turn/started",
899
+ ],
900
+ @"requestAttestation": @NO,
901
+ },
902
+ @"clientInfo": @{
903
+ @"name": @"legend-apps",
904
+ @"title": @"Legend Apps",
905
+ @"version": @"0.0.1",
906
+ },
907
+ }, deadline, cancellationGeneration);
908
+ throwForResponseError(response, "Codex app-server could not initialize");
909
+ NSDictionary* result = dictionaryValue(response[@"result"]);
910
+ std::string userAgent = toString(stringValue(result[@"userAgent"]));
911
+ if (userAgent.empty()) {
912
+ throw std::runtime_error(
913
+ "Codex app-server returned an invalid initialize response. Update Codex, then reopen the app.");
914
+ }
915
+ sendNotification(@"initialized", cancellationGeneration, deadline);
916
+ {
917
+ std::lock_guard lock(mutex_);
918
+ userAgent_ = userAgent;
919
+ initialized_ = true;
920
+ }
921
+ } catch (...) {
922
+ terminateCurrentProcess();
923
+ throw;
924
+ }
925
+ }
926
+
927
+ void startProcess() {
928
+ terminateCurrentProcess();
929
+ const std::string codexPath = resolveCodexPath();
930
+ if (codexPath.empty()) {
931
+ throw std::runtime_error(
932
+ "Codex CLI was not found. Install Codex, or set CODEX_PATH to the Codex executable, then reopen the app.");
933
+ }
934
+
935
+ int inputPipe[2] = {-1, -1};
936
+ int outputPipe[2] = {-1, -1};
937
+ int errorPipe[2] = {-1, -1};
938
+ if (pipe(inputPipe) != 0 || pipe(outputPipe) != 0 || pipe(errorPipe) != 0) {
939
+ closePipe(inputPipe);
940
+ closePipe(outputPipe);
941
+ closePipe(errorPipe);
942
+ throw std::runtime_error("Could not create pipes for Codex app-server: " + std::string(strerror(errno)));
943
+ }
944
+
945
+ posix_spawn_file_actions_t actions;
946
+ posix_spawn_file_actions_init(&actions);
947
+ posix_spawn_file_actions_adddup2(&actions, inputPipe[0], STDIN_FILENO);
948
+ posix_spawn_file_actions_adddup2(&actions, outputPipe[1], STDOUT_FILENO);
949
+ posix_spawn_file_actions_adddup2(&actions, errorPipe[1], STDERR_FILENO);
950
+ posix_spawn_file_actions_addclose(&actions, inputPipe[0]);
951
+ posix_spawn_file_actions_addclose(&actions, inputPipe[1]);
952
+ posix_spawn_file_actions_addclose(&actions, outputPipe[0]);
953
+ posix_spawn_file_actions_addclose(&actions, outputPipe[1]);
954
+ posix_spawn_file_actions_addclose(&actions, errorPipe[0]);
955
+ posix_spawn_file_actions_addclose(&actions, errorPipe[1]);
956
+ #if defined(__APPLE__)
957
+ posix_spawn_file_actions_addchdir_np(&actions, defaultWorkingDirectory().fileSystemRepresentation);
958
+ #endif
959
+
960
+ posix_spawnattr_t attributes;
961
+ posix_spawnattr_init(&attributes);
962
+ short spawnFlags = 0;
963
+ #if defined(__APPLE__) && defined(POSIX_SPAWN_CLOEXEC_DEFAULT)
964
+ spawnFlags |= POSIX_SPAWN_CLOEXEC_DEFAULT;
965
+ #endif
966
+ sigset_t emptySignalMask;
967
+ sigemptyset(&emptySignalMask);
968
+ posix_spawnattr_setsigmask(&attributes, &emptySignalMask);
969
+ spawnFlags |= POSIX_SPAWN_SETSIGMASK;
970
+
971
+ sigset_t defaultSignals;
972
+ sigfillset(&defaultSignals);
973
+ sigdelset(&defaultSignals, SIGKILL);
974
+ sigdelset(&defaultSignals, SIGSTOP);
975
+ posix_spawnattr_setsigdefault(&attributes, &defaultSignals);
976
+ spawnFlags |= POSIX_SPAWN_SETSIGDEF;
977
+ posix_spawnattr_setpgroup(&attributes, 0);
978
+ spawnFlags |= POSIX_SPAWN_SETPGROUP;
979
+ posix_spawnattr_setflags(&attributes, spawnFlags);
980
+
981
+ std::vector<std::string> arguments = {codexPath, "app-server", "--stdio"};
982
+ std::vector<char*> argumentPointers = pointerArray(arguments);
983
+ std::vector<std::string> environment = sanitizedEnvironmentStorage();
984
+ std::vector<char*> environmentPointers = pointerArray(environment);
985
+ pid_t pid = -1;
986
+ const int spawnError = posix_spawn(
987
+ &pid,
988
+ codexPath.c_str(),
989
+ &actions,
990
+ &attributes,
991
+ argumentPointers.data(),
992
+ environmentPointers.data());
993
+ posix_spawnattr_destroy(&attributes);
994
+ posix_spawn_file_actions_destroy(&actions);
995
+ close(inputPipe[0]);
996
+ close(outputPipe[1]);
997
+ close(errorPipe[1]);
998
+ if (spawnError != 0) {
999
+ close(inputPipe[1]);
1000
+ close(outputPipe[0]);
1001
+ close(errorPipe[0]);
1002
+ throw std::runtime_error("Could not start Codex app-server: " + std::string(strerror(spawnError)));
1003
+ }
1004
+
1005
+ #if defined(F_SETNOSIGPIPE)
1006
+ fcntl(inputPipe[1], F_SETNOSIGPIPE, 1);
1007
+ #endif
1008
+ const int inputFlags = fcntl(inputPipe[1], F_GETFL, 0);
1009
+ if (inputFlags >= 0) fcntl(inputPipe[1], F_SETFL, inputFlags | O_NONBLOCK);
1010
+ uint64_t generation = 0;
1011
+ {
1012
+ std::lock_guard lock(mutex_);
1013
+ generation = ++nextProcessGeneration_;
1014
+ processGeneration_ = generation;
1015
+ pid_ = pid;
1016
+ stdinFd_ = inputPipe[1];
1017
+ initialized_ = false;
1018
+ codexPath_ = codexPath;
1019
+ userAgent_.clear();
1020
+ processError_.clear();
1021
+ stderrTail_.clear();
1022
+ responses_.clear();
1023
+ ignoredResponseIds_.clear();
1024
+ turns_.clear();
1025
+ preAckBytes_ = 0;
1026
+ pendingTurnStarts_ = 0;
1027
+ retiredTurnIds_.clear();
1028
+ retiredTurnOrder_.clear();
1029
+ }
1030
+
1031
+ std::thread([this, fd = outputPipe[0], generation]() { readStdout(fd, generation); }).detach();
1032
+ std::thread([this, fd = errorPipe[0], generation]() { readStderr(fd, generation); }).detach();
1033
+ std::thread([this, pid, generation]() {
1034
+ int status = 0;
1035
+ while (waitpid(pid, &status, 0) < 0 && errno == EINTR) {
1036
+ }
1037
+ processExited(pid, generation, status);
1038
+ }).detach();
1039
+ }
1040
+
1041
+ static void closePipe(int pipeFds[2]) {
1042
+ if (pipeFds[0] >= 0) {
1043
+ close(pipeFds[0]);
1044
+ }
1045
+ if (pipeFds[1] >= 0) {
1046
+ close(pipeFds[1]);
1047
+ }
1048
+ }
1049
+
1050
+ void terminateCurrentProcess() {
1051
+ pid_t pid = -1;
1052
+ int stdinFd = -1;
1053
+ {
1054
+ std::lock_guard lock(mutex_);
1055
+ pid = pid_;
1056
+ stdinFd = stdinFd_;
1057
+ pid_ = -1;
1058
+ stdinFd_ = -1;
1059
+ initialized_ = false;
1060
+ processGeneration_ = 0;
1061
+ responses_.clear();
1062
+ ignoredResponseIds_.clear();
1063
+ turns_.clear();
1064
+ preAckBytes_ = 0;
1065
+ pendingTurnStarts_ = 0;
1066
+ condition_.notify_all();
1067
+ }
1068
+ if (stdinFd >= 0) {
1069
+ close(stdinFd);
1070
+ }
1071
+ if (pid > 0) {
1072
+ kill(-pid, SIGTERM);
1073
+ }
1074
+ }
1075
+
1076
+ void terminateProcessGeneration(uint64_t generation, const std::string& reason) {
1077
+ pid_t pid = -1;
1078
+ int stdinFd = -1;
1079
+ {
1080
+ std::lock_guard lock(mutex_);
1081
+ if (processGeneration_ != generation) return;
1082
+ pid = pid_;
1083
+ stdinFd = stdinFd_;
1084
+ pid_ = -1;
1085
+ stdinFd_ = -1;
1086
+ initialized_ = false;
1087
+ processGeneration_ = 0;
1088
+ processError_ = reason;
1089
+ turns_.clear();
1090
+ preAckBytes_ = 0;
1091
+ pendingTurnStarts_ = 0;
1092
+ condition_.notify_all();
1093
+ }
1094
+ if (stdinFd >= 0) close(stdinFd);
1095
+ if (pid > 0) kill(-pid, SIGTERM);
1096
+ }
1097
+
1098
+ NSDictionary* request(NSString* method, NSDictionary* params, Clock::time_point deadline, uint64_t cancellationGeneration = UINT64_MAX, uint64_t expectedGeneration = 0) {
1099
+ int64_t requestId = 0;
1100
+ uint64_t generation = 0;
1101
+ {
1102
+ std::lock_guard lock(mutex_);
1103
+ requestId = nextRequestId_++;
1104
+ generation = processGeneration_;
1105
+ if (expectedGeneration != 0 && generation != expectedGeneration) throw std::runtime_error("Codex app-server restarted during the request.");
1106
+ if (generation == 0 || stdinFd_ < 0) {
1107
+ throw std::runtime_error(processError_.empty() ? "Codex app-server is not running." : processError_);
1108
+ }
1109
+ }
1110
+
1111
+ sendJson(@{
1112
+ @"id": @(requestId),
1113
+ @"method": method,
1114
+ @"params": params,
1115
+ }, generation, cancellationGeneration, deadline);
1116
+
1117
+ std::string responseLine;
1118
+ {
1119
+ std::unique_lock lock(mutex_);
1120
+ const bool received = condition_.wait_until(lock, deadline, [&]() {
1121
+ return responses_.contains(requestId) || processGeneration_ != generation ||
1122
+ (cancellationGeneration != UINT64_MAX && cancelGeneration_ != cancellationGeneration);
1123
+ });
1124
+ if (!received) {
1125
+ ignoredResponseIds_.insert(requestId);
1126
+ throw std::runtime_error("Codex app-server did not respond in time.");
1127
+ }
1128
+ if (cancellationGeneration != UINT64_MAX && cancelGeneration_ != cancellationGeneration) {
1129
+ throw std::runtime_error("Codex request was cancelled.");
1130
+ }
1131
+ if (processGeneration_ != generation && !responses_.contains(requestId)) {
1132
+ throw std::runtime_error(processError_.empty() ? "Codex app-server exited unexpectedly." : processError_);
1133
+ }
1134
+ responseLine = std::move(responses_[requestId]);
1135
+ responses_.erase(requestId);
1136
+ }
1137
+ return parseJsonObject(responseLine, "Codex app-server returned invalid JSON");
1138
+ }
1139
+
1140
+ void sendNotification(NSString* method, uint64_t cancellationGeneration = UINT64_MAX, Clock::time_point deadline = Clock::time_point::max()) {
1141
+ uint64_t generation = 0;
1142
+ {
1143
+ std::lock_guard lock(mutex_);
1144
+ generation = processGeneration_;
1145
+ }
1146
+ sendJson(@{@"method": method}, generation, cancellationGeneration, deadline);
1147
+ }
1148
+
1149
+ void interruptTurn(const std::string& threadId, const std::string& turnId, uint64_t expectedGeneration) {
1150
+ int64_t requestId = 0;
1151
+ uint64_t generation = 0;
1152
+ {
1153
+ std::lock_guard lock(mutex_);
1154
+ generation = processGeneration_;
1155
+ if (generation == 0 || generation != expectedGeneration || stdinFd_ < 0) {
1156
+ return;
1157
+ }
1158
+ requestId = nextRequestId_++;
1159
+ ignoredResponseIds_.insert(requestId);
1160
+ }
1161
+ @autoreleasepool {
1162
+ try {
1163
+ sendJson(@{
1164
+ @"id": @(requestId),
1165
+ @"method": @"turn/interrupt",
1166
+ @"params": @{
1167
+ @"threadId": toNSString(threadId),
1168
+ @"turnId": toNSString(turnId),
1169
+ },
1170
+ }, generation, UINT64_MAX, Clock::now() + std::chrono::milliseconds(100));
1171
+ } catch (...) {
1172
+ }
1173
+ }
1174
+ }
1175
+
1176
+ void sendJson(NSDictionary* value, uint64_t generation, uint64_t expectedCancellationGeneration = UINT64_MAX, Clock::time_point deadline = Clock::time_point::max()) {
1177
+ std::string json = serializeJson(value);
1178
+ json.push_back('\n');
1179
+ uint64_t cancellationGeneration = 0;
1180
+ { std::lock_guard lock(mutex_); cancellationGeneration = expectedCancellationGeneration == UINT64_MAX ? cancelGeneration_ : expectedCancellationGeneration; }
1181
+ std::unique_lock writeLock(writeMutex_, std::defer_lock);
1182
+ const auto writeDeadline = std::min(deadline, Clock::now() + std::chrono::seconds(1));
1183
+ while (!writeLock.try_lock_for(std::chrono::milliseconds(10))) {
1184
+ { std::lock_guard lock(mutex_); if (generation != processGeneration_ || (cancellationGeneration != UINT64_MAX && cancelGeneration_ != cancellationGeneration)) throw std::runtime_error("Codex request was cancelled."); }
1185
+ if (Clock::now() >= writeDeadline) throw std::runtime_error("Timed out waiting to write to Codex app-server.");
1186
+ }
1187
+ int fd = -1;
1188
+ {
1189
+ std::lock_guard lock(mutex_);
1190
+ if (generation == 0 || generation != processGeneration_ || stdinFd_ < 0) {
1191
+ throw std::runtime_error(processError_.empty() ? "Codex app-server is not running." : processError_);
1192
+ }
1193
+ fd = fcntl(stdinFd_, F_DUPFD_CLOEXEC, 0);
1194
+ if (fd < 0) throw std::runtime_error("Could not duplicate Codex app-server input: " + std::string(strerror(errno)));
1195
+ }
1196
+ size_t offset = 0;
1197
+ bool reportedBlocked = false;
1198
+ try {
1199
+ while (offset < json.size()) {
1200
+ {
1201
+ std::lock_guard lock(mutex_);
1202
+ if (generation != processGeneration_ || (cancellationGeneration != UINT64_MAX && cancelGeneration_ != cancellationGeneration)) {
1203
+ throw std::runtime_error("Codex request was cancelled.");
1204
+ }
1205
+ }
1206
+ if (Clock::now() >= writeDeadline) throw std::runtime_error("Timed out writing to Codex app-server.");
1207
+ const ssize_t written = write(fd, json.data() + offset, json.size() - offset);
1208
+ if (written > 0) {
1209
+ offset += static_cast<size_t>(written);
1210
+ } else if (written < 0 && errno == EINTR) {
1211
+ continue;
1212
+ } else if (written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
1213
+ if (!reportedBlocked) {
1214
+ std::lock_guard lock(mutex_);
1215
+ ++blockedWriteCount_;
1216
+ reportedBlocked = true;
1217
+ }
1218
+ pollfd descriptor{fd, POLLOUT, 0};
1219
+ poll(&descriptor, 1, 25);
1220
+ } else {
1221
+ throw std::runtime_error("Could not write to Codex app-server: " + std::string(strerror(errno)));
1222
+ }
1223
+ }
1224
+ } catch (...) {
1225
+ close(fd);
1226
+ if (reportedBlocked) { std::lock_guard lock(mutex_); --blockedWriteCount_; }
1227
+ if (offset > 0) terminateProcessGeneration(generation, "Codex app-server request write did not complete.");
1228
+ throw;
1229
+ }
1230
+ if (reportedBlocked) { std::lock_guard lock(mutex_); --blockedWriteCount_; }
1231
+ close(fd);
1232
+ }
1233
+
1234
+ void readStdout(int fd, uint64_t generation) {
1235
+ std::string pending;
1236
+ char buffer[8192];
1237
+ while (true) {
1238
+ const ssize_t count = read(fd, buffer, sizeof(buffer));
1239
+ if (count > 0) {
1240
+ pending.append(buffer, static_cast<size_t>(count));
1241
+ if (pending.size() > kMaximumMessageBytes) {
1242
+ markProtocolFailure(generation, "Codex app-server sent a message that was too large.");
1243
+ break;
1244
+ }
1245
+ size_t newline = std::string::npos;
1246
+ while ((newline = pending.find('\n')) != std::string::npos) {
1247
+ std::string line = pending.substr(0, newline);
1248
+ pending.erase(0, newline + 1);
1249
+ if (!line.empty()) {
1250
+ handleMessage(line, generation);
1251
+ }
1252
+ }
1253
+ } else if (count < 0 && errno == EINTR) {
1254
+ continue;
1255
+ } else {
1256
+ break;
1257
+ }
1258
+ }
1259
+ close(fd);
1260
+ }
1261
+
1262
+ void readStderr(int fd, uint64_t generation) {
1263
+ char buffer[4096];
1264
+ while (true) {
1265
+ const ssize_t count = read(fd, buffer, sizeof(buffer));
1266
+ if (count > 0) {
1267
+ std::lock_guard lock(mutex_);
1268
+ if (processGeneration_ == generation) {
1269
+ stderrTail_.append(buffer, static_cast<size_t>(count));
1270
+ if (stderrTail_.size() > kMaximumStderrBytes) {
1271
+ stderrTail_.erase(0, stderrTail_.size() - kMaximumStderrBytes);
1272
+ }
1273
+ }
1274
+ } else if (count < 0 && errno == EINTR) {
1275
+ continue;
1276
+ } else {
1277
+ break;
1278
+ }
1279
+ }
1280
+ close(fd);
1281
+ }
1282
+
1283
+ void handleMessage(const std::string& line, uint64_t generation) {
1284
+ @autoreleasepool {
1285
+ NSDictionary* message = nil;
1286
+ try {
1287
+ message = parseJsonObject(line, "Codex app-server returned invalid JSON");
1288
+ } catch (const std::exception& error) {
1289
+ markProtocolFailure(generation, error.what());
1290
+ return;
1291
+ }
1292
+
1293
+ id rawRequestId = message[@"id"];
1294
+ NSNumber* numericRequestId = [rawRequestId isKindOfClass:[NSNumber class]] ? rawRequestId : nil;
1295
+ const bool hasRequestId = numericRequestId != nil || [rawRequestId isKindOfClass:[NSString class]];
1296
+ NSString* method = stringValue(message[@"method"]);
1297
+ if (numericRequestId != nil && method == nil) {
1298
+ std::lock_guard lock(mutex_);
1299
+ if (processGeneration_ != generation) {
1300
+ return;
1301
+ }
1302
+ const int64_t identifier = numericRequestId.longLongValue;
1303
+ if (ignoredResponseIds_.erase(identifier) == 0) {
1304
+ responses_[identifier] = line;
1305
+ }
1306
+ condition_.notify_all();
1307
+ return;
1308
+ }
1309
+ if (method == nil) {
1310
+ return;
1311
+ }
1312
+ if (hasRequestId) {
1313
+ rejectServerRequest(rawRequestId, generation);
1314
+ return;
1315
+ }
1316
+
1317
+ NSDictionary* params = dictionaryValue(message[@"params"]);
1318
+ if ([method isEqualToString:@"item/completed"]) {
1319
+ NSDictionary* item = dictionaryValue(params[@"item"]);
1320
+ if ([stringValue(item[@"type"]) isEqualToString:@"agentMessage"]) {
1321
+ const std::string turnId = toString(stringValue(params[@"turnId"]));
1322
+ const std::string text = toString(stringValue(item[@"text"]));
1323
+ std::lock_guard lock(mutex_);
1324
+ if (processGeneration_ == generation && !turnId.empty()) {
1325
+ if (TurnState* state = pendingOrActiveTurnLocked(turnId)) setPreAckTextLocked(*state, &TurnState::output, text);
1326
+ }
1327
+ }
1328
+ } else if ([method isEqualToString:@"error"]) {
1329
+ const std::string turnId = toString(stringValue(params[@"turnId"]));
1330
+ NSDictionary* error = dictionaryValue(params[@"error"]);
1331
+ const std::string errorMessage = toString(stringValue(error[@"message"]));
1332
+ const bool willRetry = boolValue(params[@"willRetry"]);
1333
+ std::lock_guard lock(mutex_);
1334
+ if (processGeneration_ == generation && !turnId.empty() && !willRetry) {
1335
+ if (TurnState* state = pendingOrActiveTurnLocked(turnId)) setPreAckTextLocked(*state, &TurnState::error, errorMessage);
1336
+ }
1337
+ } else if ([method isEqualToString:@"turn/completed"]) {
1338
+ NSDictionary* turn = dictionaryValue(params[@"turn"]);
1339
+ const std::string turnId = toString(stringValue(turn[@"id"]));
1340
+ const std::string status = toString(stringValue(turn[@"status"]));
1341
+ std::string output;
1342
+ for (id rawItem in arrayValue(turn[@"items"]) ?: @[]) {
1343
+ NSDictionary* item = dictionaryValue(rawItem);
1344
+ if ([stringValue(item[@"type"]) isEqualToString:@"agentMessage"]) {
1345
+ output = toString(stringValue(item[@"text"]));
1346
+ }
1347
+ }
1348
+ NSDictionary* error = dictionaryValue(turn[@"error"]);
1349
+ std::string errorMessage = toString(stringValue(error[@"message"]));
1350
+ if (errorMessage.empty() && status != "completed") {
1351
+ errorMessage = status.empty() ? "Codex turn failed." : "Codex turn " + status + ".";
1352
+ }
1353
+ std::lock_guard lock(mutex_);
1354
+ if (processGeneration_ == generation && !turnId.empty()) {
1355
+ TurnState* existing = pendingOrActiveTurnLocked(turnId); if (existing == nullptr) return;
1356
+ TurnState& state = *existing;
1357
+ if (!output.empty()) {
1358
+ setPreAckTextLocked(state, &TurnState::output, output);
1359
+ }
1360
+ setPreAckTextLocked(state, &TurnState::error, errorMessage);
1361
+ state.done = true;
1362
+ condition_.notify_all();
1363
+ }
1364
+ }
1365
+ }
1366
+ }
1367
+
1368
+ void rejectServerRequest(id requestId, uint64_t generation) {
1369
+ @autoreleasepool {
1370
+ try {
1371
+ sendJson(@{
1372
+ @"error": @{
1373
+ @"code": @(-32601),
1374
+ @"message": @"Legend Apps does not support this Codex server request.",
1375
+ },
1376
+ @"id": requestId,
1377
+ }, generation);
1378
+ } catch (...) {
1379
+ }
1380
+ }
1381
+ }
1382
+
1383
+ void markProtocolFailure(uint64_t generation, const std::string& message) {
1384
+ std::lock_guard lock(mutex_);
1385
+ if (processGeneration_ != generation) {
1386
+ return;
1387
+ }
1388
+ processError_ = actionableError(message);
1389
+ for (auto& [turnId, state] : turns_) {
1390
+ state.error = processError_;
1391
+ state.done = true;
1392
+ }
1393
+ condition_.notify_all();
1394
+ }
1395
+
1396
+ void processExited(pid_t pid, uint64_t generation, int status) {
1397
+ std::lock_guard lock(mutex_);
1398
+ if (processGeneration_ != generation || pid_ != pid) {
1399
+ return;
1400
+ }
1401
+ if (stdinFd_ >= 0) {
1402
+ close(stdinFd_);
1403
+ }
1404
+ stdinFd_ = -1;
1405
+ pid_ = -1;
1406
+ initialized_ = false;
1407
+ processGeneration_ = 0;
1408
+ std::string detail;
1409
+ if (WIFEXITED(status)) {
1410
+ detail = "Codex app-server exited with status " + std::to_string(WEXITSTATUS(status)) + ".";
1411
+ } else if (WIFSIGNALED(status)) {
1412
+ detail = "Codex app-server stopped with signal " + std::to_string(WTERMSIG(status)) + ".";
1413
+ } else {
1414
+ detail = "Codex app-server exited unexpectedly.";
1415
+ }
1416
+ if (!stderrTail_.empty()) {
1417
+ detail += " " + stderrTail_;
1418
+ }
1419
+ processError_ = actionableError(detail);
1420
+ turns_.clear();
1421
+ preAckBytes_ = 0;
1422
+ pendingTurnStarts_ = 0;
1423
+ condition_.notify_all();
1424
+ }
1425
+
1426
+ static void throwForResponseError(NSDictionary* response, const std::string& context) {
1427
+ const std::string error = responseError(response);
1428
+ if (!error.empty()) {
1429
+ throw std::runtime_error(actionableError(context + ": " + error));
1430
+ }
1431
+ if (dictionaryValue(response[@"result"]) == nil) {
1432
+ throw std::runtime_error(
1433
+ context + ": Codex returned an invalid response. Update Codex, then reopen the app.");
1434
+ }
1435
+ }
1436
+
1437
+ std::condition_variable condition_;
1438
+ std::map<int64_t, std::string> responses_;
1439
+ std::set<int64_t> ignoredResponseIds_;
1440
+ std::set<pid_t> fallbackPids_;
1441
+ std::map<std::string, TurnState> turns_;
1442
+ std::set<std::string> retiredTurnIds_;
1443
+ std::deque<std::string> retiredTurnOrder_;
1444
+ std::timed_mutex lifecycleMutex_;
1445
+ std::mutex mutex_;
1446
+ std::timed_mutex writeMutex_;
1447
+ size_t activeRuns_ = 0;
1448
+ size_t blockedWriteCount_ = 0;
1449
+ size_t preAckBytes_ = 0;
1450
+ size_t pendingTurnStarts_ = 0;
1451
+ std::string codexPath_;
1452
+ std::string processError_;
1453
+ std::string stderrTail_;
1454
+ std::string userAgent_;
1455
+ int stdinFd_ = -1;
1456
+ int64_t nextRequestId_ = 1;
1457
+ pid_t pid_ = -1;
1458
+ uint64_t cancelGeneration_ = 0;
1459
+ uint64_t nextProcessGeneration_ = 0;
1460
+ uint64_t nextTurnSequence_ = 0;
1461
+ uint64_t processGeneration_ = 0;
1462
+ bool initialized_ = false;
1463
+ bool useExecFallback_ = false;
1464
+ };
1465
+
1466
+ } // namespace
1467
+
1468
+ #if defined(SPARK_CODEX_TESTING)
1469
+ extern "C" int SparkCodexTestRunPrompt(const char* prompt, double timeoutMs, char* output, size_t outputCapacity, char* error, size_t errorCapacity) {
1470
+ auto copy = [](char* target, size_t capacity, const std::string& value) {
1471
+ if (capacity == 0) return;
1472
+ const size_t length = std::min(capacity - 1, value.size());
1473
+ std::memcpy(target, value.data(), length); target[length] = '\0';
1474
+ };
1475
+ try {
1476
+ const auto result = CodexProcessSupervisor::shared().runPrompt(prompt ?: "", "", "low", timeoutMs, "", "");
1477
+ copy(output, outputCapacity, result.output);
1478
+ return 0;
1479
+ } catch (const std::exception& exception) { copy(error, errorCapacity, exception.what()); return 1; }
1480
+ catch (...) { copy(error, errorCapacity, "unknown native failure"); return 1; }
1481
+ }
1482
+ extern "C" double SparkCodexTestCancel() { return CodexProcessSupervisor::shared().cancelActiveRuns(); }
1483
+ extern "C" double SparkCodexTestShutdown() { return CodexProcessSupervisor::shared().shutdown(); }
1484
+ extern "C" size_t SparkCodexTestRetainedTurns() { return CodexProcessSupervisor::shared().retainedTurnCountForTesting(); }
1485
+ extern "C" size_t SparkCodexTestBlockedWrites() { return CodexProcessSupervisor::shared().blockedWriteCountForTesting(); }
1486
+ #else
1487
+
1488
+ HybridCodexAppServer::HybridCodexAppServer() : HybridObject(TAG) {}
1489
+
1490
+ std::shared_ptr<Promise<CodexAvailability>> HybridCodexAppServer::getAvailability() {
1491
+ return Promise<CodexAvailability>::async([]() {
1492
+ return CodexProcessSupervisor::shared().getAvailability();
1493
+ });
1494
+ }
1495
+
1496
+ std::shared_ptr<Promise<CodexRunResult>> HybridCodexAppServer::runPrompt(
1497
+ const std::string& prompt,
1498
+ const std::string& cwd,
1499
+ const std::string& reasoningEffort,
1500
+ double timeoutMs,
1501
+ const std::string& outputSchemaJson,
1502
+ const std::string& developerInstructions) {
1503
+ return Promise<CodexRunResult>::async([
1504
+ prompt,
1505
+ cwd,
1506
+ reasoningEffort,
1507
+ timeoutMs,
1508
+ outputSchemaJson,
1509
+ developerInstructions]() {
1510
+ return CodexProcessSupervisor::shared().runPrompt(
1511
+ prompt,
1512
+ cwd,
1513
+ reasoningEffort,
1514
+ timeoutMs,
1515
+ outputSchemaJson,
1516
+ developerInstructions);
1517
+ });
1518
+ }
1519
+
1520
+ double HybridCodexAppServer::cancelActiveRuns() {
1521
+ return CodexProcessSupervisor::shared().cancelActiveRuns();
1522
+ }
1523
+
1524
+ double HybridCodexAppServer::shutdown() {
1525
+ return CodexProcessSupervisor::shared().shutdown();
1526
+ }
1527
+ #endif
1528
+
1529
+ } // namespace margelo::nitro::legendapps::codex