better-codex 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (405) hide show
  1. package/README.md +26 -0
  2. package/apps/backend/README.md +46 -0
  3. package/apps/backend/bun.lock +64 -0
  4. package/apps/backend/package.json +18 -0
  5. package/apps/backend/scripts/generate-protocol.ts +32 -0
  6. package/apps/backend/src/analytics/service.ts +219 -0
  7. package/apps/backend/src/analytics/store.ts +284 -0
  8. package/apps/backend/src/config.ts +98 -0
  9. package/apps/backend/src/core/app-server.ts +131 -0
  10. package/apps/backend/src/core/jsonrpc.ts +166 -0
  11. package/apps/backend/src/protocol/AbsolutePathBuf.ts +14 -0
  12. package/apps/backend/src/protocol/AddConversationListenerParams.ts +6 -0
  13. package/apps/backend/src/protocol/AddConversationSubscriptionResponse.ts +5 -0
  14. package/apps/backend/src/protocol/AgentMessageContent.ts +5 -0
  15. package/apps/backend/src/protocol/AgentMessageContentDeltaEvent.ts +5 -0
  16. package/apps/backend/src/protocol/AgentMessageDeltaEvent.ts +5 -0
  17. package/apps/backend/src/protocol/AgentMessageEvent.ts +5 -0
  18. package/apps/backend/src/protocol/AgentMessageItem.ts +6 -0
  19. package/apps/backend/src/protocol/AgentReasoningDeltaEvent.ts +5 -0
  20. package/apps/backend/src/protocol/AgentReasoningEvent.ts +5 -0
  21. package/apps/backend/src/protocol/AgentReasoningRawContentDeltaEvent.ts +5 -0
  22. package/apps/backend/src/protocol/AgentReasoningRawContentEvent.ts +5 -0
  23. package/apps/backend/src/protocol/AgentReasoningSectionBreakEvent.ts +5 -0
  24. package/apps/backend/src/protocol/Annotations.ts +9 -0
  25. package/apps/backend/src/protocol/ApplyPatchApprovalParams.ts +21 -0
  26. package/apps/backend/src/protocol/ApplyPatchApprovalRequestEvent.ts +23 -0
  27. package/apps/backend/src/protocol/ApplyPatchApprovalResponse.ts +6 -0
  28. package/apps/backend/src/protocol/ArchiveConversationParams.ts +6 -0
  29. package/apps/backend/src/protocol/ArchiveConversationResponse.ts +5 -0
  30. package/apps/backend/src/protocol/AskForApproval.ts +9 -0
  31. package/apps/backend/src/protocol/AudioContent.ts +9 -0
  32. package/apps/backend/src/protocol/AuthMode.ts +5 -0
  33. package/apps/backend/src/protocol/AuthStatusChangeNotification.ts +9 -0
  34. package/apps/backend/src/protocol/BackgroundEventEvent.ts +5 -0
  35. package/apps/backend/src/protocol/BlobResourceContents.ts +5 -0
  36. package/apps/backend/src/protocol/CallToolResult.ts +10 -0
  37. package/apps/backend/src/protocol/CancelLoginChatGptParams.ts +5 -0
  38. package/apps/backend/src/protocol/CancelLoginChatGptResponse.ts +5 -0
  39. package/apps/backend/src/protocol/ClientInfo.ts +5 -0
  40. package/apps/backend/src/protocol/ClientNotification.ts +5 -0
  41. package/apps/backend/src/protocol/ClientRequest.ts +46 -0
  42. package/apps/backend/src/protocol/CodexErrorInfo.ts +8 -0
  43. package/apps/backend/src/protocol/ContentBlock.ts +10 -0
  44. package/apps/backend/src/protocol/ContentItem.ts +5 -0
  45. package/apps/backend/src/protocol/ContextCompactedEvent.ts +5 -0
  46. package/apps/backend/src/protocol/ConversationGitInfo.ts +5 -0
  47. package/apps/backend/src/protocol/ConversationId.ts +5 -0
  48. package/apps/backend/src/protocol/ConversationSummary.ts +8 -0
  49. package/apps/backend/src/protocol/CreditsSnapshot.ts +5 -0
  50. package/apps/backend/src/protocol/CustomPrompt.ts +5 -0
  51. package/apps/backend/src/protocol/DeprecationNoticeEvent.ts +13 -0
  52. package/apps/backend/src/protocol/ElicitationRequestEvent.ts +6 -0
  53. package/apps/backend/src/protocol/EmbeddedResource.ts +13 -0
  54. package/apps/backend/src/protocol/EmbeddedResourceResource.ts +7 -0
  55. package/apps/backend/src/protocol/ErrorEvent.ts +6 -0
  56. package/apps/backend/src/protocol/EventMsg.ts +60 -0
  57. package/apps/backend/src/protocol/ExecApprovalRequestEvent.ts +32 -0
  58. package/apps/backend/src/protocol/ExecCommandApprovalParams.ts +12 -0
  59. package/apps/backend/src/protocol/ExecCommandApprovalResponse.ts +6 -0
  60. package/apps/backend/src/protocol/ExecCommandBeginEvent.ts +35 -0
  61. package/apps/backend/src/protocol/ExecCommandEndEvent.ts +59 -0
  62. package/apps/backend/src/protocol/ExecCommandOutputDeltaEvent.ts +18 -0
  63. package/apps/backend/src/protocol/ExecCommandSource.ts +5 -0
  64. package/apps/backend/src/protocol/ExecOneOffCommandParams.ts +6 -0
  65. package/apps/backend/src/protocol/ExecOneOffCommandResponse.ts +5 -0
  66. package/apps/backend/src/protocol/ExecOutputStream.ts +5 -0
  67. package/apps/backend/src/protocol/ExecPolicyAmendment.ts +12 -0
  68. package/apps/backend/src/protocol/ExitedReviewModeEvent.ts +6 -0
  69. package/apps/backend/src/protocol/FileChange.ts +5 -0
  70. package/apps/backend/src/protocol/ForcedLoginMethod.ts +5 -0
  71. package/apps/backend/src/protocol/FunctionCallOutputContentItem.ts +9 -0
  72. package/apps/backend/src/protocol/FunctionCallOutputPayload.ts +15 -0
  73. package/apps/backend/src/protocol/FuzzyFileSearchParams.ts +5 -0
  74. package/apps/backend/src/protocol/FuzzyFileSearchResponse.ts +6 -0
  75. package/apps/backend/src/protocol/FuzzyFileSearchResult.ts +8 -0
  76. package/apps/backend/src/protocol/GetAuthStatusParams.ts +5 -0
  77. package/apps/backend/src/protocol/GetAuthStatusResponse.ts +6 -0
  78. package/apps/backend/src/protocol/GetConversationSummaryParams.ts +6 -0
  79. package/apps/backend/src/protocol/GetConversationSummaryResponse.ts +6 -0
  80. package/apps/backend/src/protocol/GetHistoryEntryResponseEvent.ts +10 -0
  81. package/apps/backend/src/protocol/GetUserAgentResponse.ts +5 -0
  82. package/apps/backend/src/protocol/GetUserSavedConfigResponse.ts +6 -0
  83. package/apps/backend/src/protocol/GhostCommit.ts +8 -0
  84. package/apps/backend/src/protocol/GitDiffToRemoteParams.ts +5 -0
  85. package/apps/backend/src/protocol/GitDiffToRemoteResponse.ts +6 -0
  86. package/apps/backend/src/protocol/GitSha.ts +5 -0
  87. package/apps/backend/src/protocol/HistoryEntry.ts +5 -0
  88. package/apps/backend/src/protocol/ImageContent.ts +9 -0
  89. package/apps/backend/src/protocol/InitializeParams.ts +6 -0
  90. package/apps/backend/src/protocol/InitializeResponse.ts +5 -0
  91. package/apps/backend/src/protocol/InputItem.ts +5 -0
  92. package/apps/backend/src/protocol/InterruptConversationParams.ts +6 -0
  93. package/apps/backend/src/protocol/InterruptConversationResponse.ts +6 -0
  94. package/apps/backend/src/protocol/ItemCompletedEvent.ts +7 -0
  95. package/apps/backend/src/protocol/ItemStartedEvent.ts +7 -0
  96. package/apps/backend/src/protocol/ListConversationsParams.ts +5 -0
  97. package/apps/backend/src/protocol/ListConversationsResponse.ts +6 -0
  98. package/apps/backend/src/protocol/ListCustomPromptsResponseEvent.ts +9 -0
  99. package/apps/backend/src/protocol/ListSkillsResponseEvent.ts +9 -0
  100. package/apps/backend/src/protocol/LocalShellAction.ts +6 -0
  101. package/apps/backend/src/protocol/LocalShellExecAction.ts +5 -0
  102. package/apps/backend/src/protocol/LocalShellStatus.ts +5 -0
  103. package/apps/backend/src/protocol/LoginApiKeyParams.ts +5 -0
  104. package/apps/backend/src/protocol/LoginApiKeyResponse.ts +5 -0
  105. package/apps/backend/src/protocol/LoginChatGptCompleteNotification.ts +8 -0
  106. package/apps/backend/src/protocol/LoginChatGptResponse.ts +5 -0
  107. package/apps/backend/src/protocol/LogoutChatGptResponse.ts +5 -0
  108. package/apps/backend/src/protocol/McpAuthStatus.ts +5 -0
  109. package/apps/backend/src/protocol/McpInvocation.ts +18 -0
  110. package/apps/backend/src/protocol/McpListToolsResponseEvent.ts +25 -0
  111. package/apps/backend/src/protocol/McpStartupCompleteEvent.ts +6 -0
  112. package/apps/backend/src/protocol/McpStartupFailure.ts +5 -0
  113. package/apps/backend/src/protocol/McpStartupStatus.ts +5 -0
  114. package/apps/backend/src/protocol/McpStartupUpdateEvent.ts +14 -0
  115. package/apps/backend/src/protocol/McpToolCallBeginEvent.ts +10 -0
  116. package/apps/backend/src/protocol/McpToolCallEndEvent.ts +15 -0
  117. package/apps/backend/src/protocol/NetworkAccess.ts +8 -0
  118. package/apps/backend/src/protocol/NewConversationParams.ts +8 -0
  119. package/apps/backend/src/protocol/NewConversationResponse.ts +7 -0
  120. package/apps/backend/src/protocol/ParsedCommand.ts +12 -0
  121. package/apps/backend/src/protocol/PatchApplyBeginEvent.ts +23 -0
  122. package/apps/backend/src/protocol/PatchApplyEndEvent.ts +31 -0
  123. package/apps/backend/src/protocol/PlanItemArg.ts +6 -0
  124. package/apps/backend/src/protocol/PlanType.ts +5 -0
  125. package/apps/backend/src/protocol/Profile.ts +9 -0
  126. package/apps/backend/src/protocol/README.md +11 -0
  127. package/apps/backend/src/protocol/RateLimitSnapshot.ts +8 -0
  128. package/apps/backend/src/protocol/RateLimitWindow.ts +17 -0
  129. package/apps/backend/src/protocol/RawResponseItemEvent.ts +6 -0
  130. package/apps/backend/src/protocol/ReasoningContentDeltaEvent.ts +5 -0
  131. package/apps/backend/src/protocol/ReasoningEffort.ts +8 -0
  132. package/apps/backend/src/protocol/ReasoningItem.ts +5 -0
  133. package/apps/backend/src/protocol/ReasoningItemContent.ts +5 -0
  134. package/apps/backend/src/protocol/ReasoningItemReasoningSummary.ts +5 -0
  135. package/apps/backend/src/protocol/ReasoningRawContentDeltaEvent.ts +5 -0
  136. package/apps/backend/src/protocol/ReasoningSummary.ts +10 -0
  137. package/apps/backend/src/protocol/RemoveConversationListenerParams.ts +5 -0
  138. package/apps/backend/src/protocol/RemoveConversationSubscriptionResponse.ts +5 -0
  139. package/apps/backend/src/protocol/RequestId.ts +5 -0
  140. package/apps/backend/src/protocol/Resource.ts +9 -0
  141. package/apps/backend/src/protocol/ResourceLink.ts +11 -0
  142. package/apps/backend/src/protocol/ResourceTemplate.ts +9 -0
  143. package/apps/backend/src/protocol/ResponseItem.ts +17 -0
  144. package/apps/backend/src/protocol/ResumeConversationParams.ts +8 -0
  145. package/apps/backend/src/protocol/ResumeConversationResponse.ts +7 -0
  146. package/apps/backend/src/protocol/ReviewCodeLocation.ts +9 -0
  147. package/apps/backend/src/protocol/ReviewDecision.ts +9 -0
  148. package/apps/backend/src/protocol/ReviewFinding.ts +9 -0
  149. package/apps/backend/src/protocol/ReviewLineRange.ts +8 -0
  150. package/apps/backend/src/protocol/ReviewOutputEvent.ts +9 -0
  151. package/apps/backend/src/protocol/ReviewRequest.ts +9 -0
  152. package/apps/backend/src/protocol/ReviewTarget.ts +9 -0
  153. package/apps/backend/src/protocol/Role.ts +8 -0
  154. package/apps/backend/src/protocol/SandboxMode.ts +5 -0
  155. package/apps/backend/src/protocol/SandboxPolicy.ts +35 -0
  156. package/apps/backend/src/protocol/SandboxSettings.ts +6 -0
  157. package/apps/backend/src/protocol/SendUserMessageParams.ts +7 -0
  158. package/apps/backend/src/protocol/SendUserMessageResponse.ts +5 -0
  159. package/apps/backend/src/protocol/SendUserTurnParams.ts +11 -0
  160. package/apps/backend/src/protocol/SendUserTurnResponse.ts +5 -0
  161. package/apps/backend/src/protocol/ServerNotification.ts +36 -0
  162. package/apps/backend/src/protocol/ServerRequest.ts +13 -0
  163. package/apps/backend/src/protocol/SessionConfiguredEvent.ts +48 -0
  164. package/apps/backend/src/protocol/SessionConfiguredNotification.ts +8 -0
  165. package/apps/backend/src/protocol/SessionSource.ts +6 -0
  166. package/apps/backend/src/protocol/SetDefaultModelParams.ts +6 -0
  167. package/apps/backend/src/protocol/SetDefaultModelResponse.ts +5 -0
  168. package/apps/backend/src/protocol/SkillErrorInfo.ts +5 -0
  169. package/apps/backend/src/protocol/SkillMetadata.ts +6 -0
  170. package/apps/backend/src/protocol/SkillScope.ts +5 -0
  171. package/apps/backend/src/protocol/SkillsListEntry.ts +7 -0
  172. package/apps/backend/src/protocol/StepStatus.ts +5 -0
  173. package/apps/backend/src/protocol/StreamErrorEvent.ts +6 -0
  174. package/apps/backend/src/protocol/SubAgentSource.ts +5 -0
  175. package/apps/backend/src/protocol/TaskCompleteEvent.ts +5 -0
  176. package/apps/backend/src/protocol/TaskStartedEvent.ts +5 -0
  177. package/apps/backend/src/protocol/TerminalInteractionEvent.ts +17 -0
  178. package/apps/backend/src/protocol/TextContent.ts +9 -0
  179. package/apps/backend/src/protocol/TextResourceContents.ts +5 -0
  180. package/apps/backend/src/protocol/TokenCountEvent.ts +7 -0
  181. package/apps/backend/src/protocol/TokenUsage.ts +5 -0
  182. package/apps/backend/src/protocol/TokenUsageInfo.ts +6 -0
  183. package/apps/backend/src/protocol/Tool.ts +11 -0
  184. package/apps/backend/src/protocol/ToolAnnotations.ts +15 -0
  185. package/apps/backend/src/protocol/ToolInputSchema.ts +9 -0
  186. package/apps/backend/src/protocol/ToolOutputSchema.ts +10 -0
  187. package/apps/backend/src/protocol/Tools.ts +5 -0
  188. package/apps/backend/src/protocol/TurnAbortReason.ts +5 -0
  189. package/apps/backend/src/protocol/TurnAbortedEvent.ts +6 -0
  190. package/apps/backend/src/protocol/TurnDiffEvent.ts +5 -0
  191. package/apps/backend/src/protocol/TurnItem.ts +9 -0
  192. package/apps/backend/src/protocol/UndoCompletedEvent.ts +5 -0
  193. package/apps/backend/src/protocol/UndoStartedEvent.ts +5 -0
  194. package/apps/backend/src/protocol/UpdatePlanArgs.ts +6 -0
  195. package/apps/backend/src/protocol/UserInfoResponse.ts +5 -0
  196. package/apps/backend/src/protocol/UserInput.ts +8 -0
  197. package/apps/backend/src/protocol/UserMessageEvent.ts +5 -0
  198. package/apps/backend/src/protocol/UserMessageItem.ts +6 -0
  199. package/apps/backend/src/protocol/UserSavedConfig.ts +14 -0
  200. package/apps/backend/src/protocol/Verbosity.ts +9 -0
  201. package/apps/backend/src/protocol/ViewImageToolCallEvent.ts +13 -0
  202. package/apps/backend/src/protocol/WarningEvent.ts +5 -0
  203. package/apps/backend/src/protocol/WebSearchAction.ts +5 -0
  204. package/apps/backend/src/protocol/WebSearchBeginEvent.ts +5 -0
  205. package/apps/backend/src/protocol/WebSearchEndEvent.ts +5 -0
  206. package/apps/backend/src/protocol/WebSearchItem.ts +5 -0
  207. package/apps/backend/src/protocol/index.ts +198 -0
  208. package/apps/backend/src/protocol/serde_json/JsonValue.ts +5 -0
  209. package/apps/backend/src/protocol/v2/Account.ts +6 -0
  210. package/apps/backend/src/protocol/v2/AccountLoginCompletedNotification.ts +5 -0
  211. package/apps/backend/src/protocol/v2/AccountRateLimitsUpdatedNotification.ts +6 -0
  212. package/apps/backend/src/protocol/v2/AccountUpdatedNotification.ts +6 -0
  213. package/apps/backend/src/protocol/v2/AgentMessageDeltaNotification.ts +5 -0
  214. package/apps/backend/src/protocol/v2/ApprovalDecision.ts +6 -0
  215. package/apps/backend/src/protocol/v2/AskForApproval.ts +5 -0
  216. package/apps/backend/src/protocol/v2/CancelLoginAccountParams.ts +5 -0
  217. package/apps/backend/src/protocol/v2/CancelLoginAccountResponse.ts +6 -0
  218. package/apps/backend/src/protocol/v2/CancelLoginAccountStatus.ts +5 -0
  219. package/apps/backend/src/protocol/v2/CodexErrorInfo.ts +11 -0
  220. package/apps/backend/src/protocol/v2/CommandAction.ts +5 -0
  221. package/apps/backend/src/protocol/v2/CommandExecParams.ts +6 -0
  222. package/apps/backend/src/protocol/v2/CommandExecResponse.ts +5 -0
  223. package/apps/backend/src/protocol/v2/CommandExecutionOutputDeltaNotification.ts +5 -0
  224. package/apps/backend/src/protocol/v2/CommandExecutionRequestApprovalParams.ts +14 -0
  225. package/apps/backend/src/protocol/v2/CommandExecutionRequestApprovalResponse.ts +6 -0
  226. package/apps/backend/src/protocol/v2/CommandExecutionStatus.ts +5 -0
  227. package/apps/backend/src/protocol/v2/Config.ts +15 -0
  228. package/apps/backend/src/protocol/v2/ConfigBatchWriteParams.ts +10 -0
  229. package/apps/backend/src/protocol/v2/ConfigEdit.ts +7 -0
  230. package/apps/backend/src/protocol/v2/ConfigLayer.ts +7 -0
  231. package/apps/backend/src/protocol/v2/ConfigLayerMetadata.ts +6 -0
  232. package/apps/backend/src/protocol/v2/ConfigLayerSource.ts +6 -0
  233. package/apps/backend/src/protocol/v2/ConfigReadParams.ts +5 -0
  234. package/apps/backend/src/protocol/v2/ConfigReadResponse.ts +8 -0
  235. package/apps/backend/src/protocol/v2/ConfigValueWriteParams.ts +11 -0
  236. package/apps/backend/src/protocol/v2/ConfigWriteResponse.ts +12 -0
  237. package/apps/backend/src/protocol/v2/ContextCompactedNotification.ts +5 -0
  238. package/apps/backend/src/protocol/v2/CreditsSnapshot.ts +5 -0
  239. package/apps/backend/src/protocol/v2/DeprecationNoticeNotification.ts +13 -0
  240. package/apps/backend/src/protocol/v2/ErrorNotification.ts +6 -0
  241. package/apps/backend/src/protocol/v2/ExecPolicyAmendment.ts +5 -0
  242. package/apps/backend/src/protocol/v2/FeedbackUploadParams.ts +5 -0
  243. package/apps/backend/src/protocol/v2/FeedbackUploadResponse.ts +5 -0
  244. package/apps/backend/src/protocol/v2/FileChangeOutputDeltaNotification.ts +5 -0
  245. package/apps/backend/src/protocol/v2/FileChangeRequestApprovalParams.ts +14 -0
  246. package/apps/backend/src/protocol/v2/FileChangeRequestApprovalResponse.ts +6 -0
  247. package/apps/backend/src/protocol/v2/FileUpdateChange.ts +6 -0
  248. package/apps/backend/src/protocol/v2/GetAccountParams.ts +5 -0
  249. package/apps/backend/src/protocol/v2/GetAccountRateLimitsResponse.ts +6 -0
  250. package/apps/backend/src/protocol/v2/GetAccountResponse.ts +6 -0
  251. package/apps/backend/src/protocol/v2/GitInfo.ts +5 -0
  252. package/apps/backend/src/protocol/v2/ItemCompletedNotification.ts +6 -0
  253. package/apps/backend/src/protocol/v2/ItemStartedNotification.ts +6 -0
  254. package/apps/backend/src/protocol/v2/ListMcpServerStatusParams.ts +13 -0
  255. package/apps/backend/src/protocol/v2/ListMcpServerStatusResponse.ts +11 -0
  256. package/apps/backend/src/protocol/v2/LoginAccountParams.ts +5 -0
  257. package/apps/backend/src/protocol/v2/LoginAccountResponse.ts +9 -0
  258. package/apps/backend/src/protocol/v2/LogoutAccountResponse.ts +5 -0
  259. package/apps/backend/src/protocol/v2/McpAuthStatus.ts +5 -0
  260. package/apps/backend/src/protocol/v2/McpServerOauthLoginCompletedNotification.ts +5 -0
  261. package/apps/backend/src/protocol/v2/McpServerOauthLoginParams.ts +5 -0
  262. package/apps/backend/src/protocol/v2/McpServerOauthLoginResponse.ts +5 -0
  263. package/apps/backend/src/protocol/v2/McpServerStatus.ts +9 -0
  264. package/apps/backend/src/protocol/v2/McpToolCallError.ts +5 -0
  265. package/apps/backend/src/protocol/v2/McpToolCallProgressNotification.ts +5 -0
  266. package/apps/backend/src/protocol/v2/McpToolCallResult.ts +7 -0
  267. package/apps/backend/src/protocol/v2/McpToolCallStatus.ts +5 -0
  268. package/apps/backend/src/protocol/v2/MergeStrategy.ts +5 -0
  269. package/apps/backend/src/protocol/v2/Model.ts +7 -0
  270. package/apps/backend/src/protocol/v2/ModelListParams.ts +13 -0
  271. package/apps/backend/src/protocol/v2/ModelListResponse.ts +11 -0
  272. package/apps/backend/src/protocol/v2/NetworkAccess.ts +5 -0
  273. package/apps/backend/src/protocol/v2/OverriddenMetadata.ts +7 -0
  274. package/apps/backend/src/protocol/v2/PatchApplyStatus.ts +5 -0
  275. package/apps/backend/src/protocol/v2/PatchChangeKind.ts +5 -0
  276. package/apps/backend/src/protocol/v2/ProfileV2.ts +10 -0
  277. package/apps/backend/src/protocol/v2/RateLimitSnapshot.ts +8 -0
  278. package/apps/backend/src/protocol/v2/RateLimitWindow.ts +5 -0
  279. package/apps/backend/src/protocol/v2/RawResponseItemCompletedNotification.ts +6 -0
  280. package/apps/backend/src/protocol/v2/ReasoningEffortOption.ts +6 -0
  281. package/apps/backend/src/protocol/v2/ReasoningSummaryPartAddedNotification.ts +5 -0
  282. package/apps/backend/src/protocol/v2/ReasoningSummaryTextDeltaNotification.ts +5 -0
  283. package/apps/backend/src/protocol/v2/ReasoningTextDeltaNotification.ts +5 -0
  284. package/apps/backend/src/protocol/v2/ReviewDelivery.ts +5 -0
  285. package/apps/backend/src/protocol/v2/ReviewStartParams.ts +12 -0
  286. package/apps/backend/src/protocol/v2/ReviewStartResponse.ts +13 -0
  287. package/apps/backend/src/protocol/v2/ReviewTarget.ts +9 -0
  288. package/apps/backend/src/protocol/v2/SandboxMode.ts +5 -0
  289. package/apps/backend/src/protocol/v2/SandboxPolicy.ts +7 -0
  290. package/apps/backend/src/protocol/v2/SandboxWorkspaceWrite.ts +5 -0
  291. package/apps/backend/src/protocol/v2/SessionSource.ts +5 -0
  292. package/apps/backend/src/protocol/v2/SkillErrorInfo.ts +5 -0
  293. package/apps/backend/src/protocol/v2/SkillMetadata.ts +6 -0
  294. package/apps/backend/src/protocol/v2/SkillScope.ts +5 -0
  295. package/apps/backend/src/protocol/v2/SkillsListEntry.ts +7 -0
  296. package/apps/backend/src/protocol/v2/SkillsListParams.ts +13 -0
  297. package/apps/backend/src/protocol/v2/SkillsListResponse.ts +6 -0
  298. package/apps/backend/src/protocol/v2/TerminalInteractionNotification.ts +5 -0
  299. package/apps/backend/src/protocol/v2/Thread.ts +46 -0
  300. package/apps/backend/src/protocol/v2/ThreadArchiveParams.ts +5 -0
  301. package/apps/backend/src/protocol/v2/ThreadArchiveResponse.ts +5 -0
  302. package/apps/backend/src/protocol/v2/ThreadItem.ts +48 -0
  303. package/apps/backend/src/protocol/v2/ThreadListParams.ts +18 -0
  304. package/apps/backend/src/protocol/v2/ThreadListResponse.ts +11 -0
  305. package/apps/backend/src/protocol/v2/ThreadResumeParams.ts +35 -0
  306. package/apps/backend/src/protocol/v2/ThreadResumeResponse.ts +9 -0
  307. package/apps/backend/src/protocol/v2/ThreadStartParams.ts +15 -0
  308. package/apps/backend/src/protocol/v2/ThreadStartResponse.ts +9 -0
  309. package/apps/backend/src/protocol/v2/ThreadStartedNotification.ts +6 -0
  310. package/apps/backend/src/protocol/v2/ThreadTokenUsage.ts +6 -0
  311. package/apps/backend/src/protocol/v2/ThreadTokenUsageUpdatedNotification.ts +6 -0
  312. package/apps/backend/src/protocol/v2/TokenUsageBreakdown.ts +5 -0
  313. package/apps/backend/src/protocol/v2/ToolsV2.ts +5 -0
  314. package/apps/backend/src/protocol/v2/Turn.ts +18 -0
  315. package/apps/backend/src/protocol/v2/TurnCompletedNotification.ts +6 -0
  316. package/apps/backend/src/protocol/v2/TurnDiffUpdatedNotification.ts +9 -0
  317. package/apps/backend/src/protocol/v2/TurnError.ts +6 -0
  318. package/apps/backend/src/protocol/v2/TurnInterruptParams.ts +5 -0
  319. package/apps/backend/src/protocol/v2/TurnInterruptResponse.ts +5 -0
  320. package/apps/backend/src/protocol/v2/TurnPlanStep.ts +6 -0
  321. package/apps/backend/src/protocol/v2/TurnPlanStepStatus.ts +5 -0
  322. package/apps/backend/src/protocol/v2/TurnPlanUpdatedNotification.ts +6 -0
  323. package/apps/backend/src/protocol/v2/TurnStartParams.ts +34 -0
  324. package/apps/backend/src/protocol/v2/TurnStartResponse.ts +6 -0
  325. package/apps/backend/src/protocol/v2/TurnStartedNotification.ts +6 -0
  326. package/apps/backend/src/protocol/v2/TurnStatus.ts +5 -0
  327. package/apps/backend/src/protocol/v2/UserInput.ts +5 -0
  328. package/apps/backend/src/protocol/v2/WindowsWorldWritableWarningNotification.ts +5 -0
  329. package/apps/backend/src/protocol/v2/WriteStatus.ts +5 -0
  330. package/apps/backend/src/protocol/v2/index.ts +123 -0
  331. package/apps/backend/src/reviews/service.ts +27 -0
  332. package/apps/backend/src/reviews/store.ts +124 -0
  333. package/apps/backend/src/server.ts +531 -0
  334. package/apps/backend/src/services/profile-store.ts +114 -0
  335. package/apps/backend/src/services/supervisor.ts +102 -0
  336. package/apps/backend/src/thread-index/service.ts +75 -0
  337. package/apps/backend/src/thread-index/store.ts +195 -0
  338. package/apps/backend/src/ws/messages.ts +73 -0
  339. package/apps/backend/tsconfig.json +20 -0
  340. package/apps/web/README.md +24 -0
  341. package/apps/web/bun.lock +1062 -0
  342. package/apps/web/eslint.config.js +23 -0
  343. package/apps/web/index.html +16 -0
  344. package/apps/web/package.json +38 -0
  345. package/apps/web/src/app.tsx +83 -0
  346. package/apps/web/src/components/composer/slash-command-menu.tsx +47 -0
  347. package/apps/web/src/components/index.ts +2 -0
  348. package/apps/web/src/components/layout/account-usage-panel.tsx +167 -0
  349. package/apps/web/src/components/layout/analytics-view.tsx +296 -0
  350. package/apps/web/src/components/layout/index.ts +7 -0
  351. package/apps/web/src/components/layout/mobile-header.tsx +56 -0
  352. package/apps/web/src/components/layout/reviews-view.tsx +848 -0
  353. package/apps/web/src/components/layout/session-view.tsx +1374 -0
  354. package/apps/web/src/components/layout/settings-dialog.tsx +322 -0
  355. package/apps/web/src/components/layout/side-bar.tsx +417 -0
  356. package/apps/web/src/components/layout/thread-list.tsx +488 -0
  357. package/apps/web/src/components/layout/virtualized-message-list.tsx +748 -0
  358. package/apps/web/src/components/loading/startup-ascii.ts +652 -0
  359. package/apps/web/src/components/loading/startup-loader.tsx +37 -0
  360. package/apps/web/src/components/session-view/file-mention-menu.tsx +46 -0
  361. package/apps/web/src/components/session-view/session-auth-banner.tsx +61 -0
  362. package/apps/web/src/components/session-view/session-composer.tsx +328 -0
  363. package/apps/web/src/components/session-view/session-dialogs.tsx +280 -0
  364. package/apps/web/src/components/session-view/session-empty.tsx +47 -0
  365. package/apps/web/src/components/session-view/session-header.tsx +49 -0
  366. package/apps/web/src/components/ui/avatar.tsx +19 -0
  367. package/apps/web/src/components/ui/badge.tsx +21 -0
  368. package/apps/web/src/components/ui/button.tsx +47 -0
  369. package/apps/web/src/components/ui/collapsible-content.tsx +114 -0
  370. package/apps/web/src/components/ui/contribution-graph.tsx +182 -0
  371. package/apps/web/src/components/ui/dialog-box.tsx +203 -0
  372. package/apps/web/src/components/ui/icon-button.tsx +32 -0
  373. package/apps/web/src/components/ui/icons.tsx +187 -0
  374. package/apps/web/src/components/ui/index.tsx +15 -0
  375. package/apps/web/src/components/ui/input.tsx +43 -0
  376. package/apps/web/src/components/ui/markdown-stream.tsx +21 -0
  377. package/apps/web/src/components/ui/mobile-drawer.tsx +124 -0
  378. package/apps/web/src/components/ui/section-header.tsx +13 -0
  379. package/apps/web/src/components/ui/select.tsx +217 -0
  380. package/apps/web/src/components/ui/shimmer.tsx +138 -0
  381. package/apps/web/src/components/ui/status-dot.tsx +24 -0
  382. package/apps/web/src/config.ts +5 -0
  383. package/apps/web/src/hooks/index.ts +3 -0
  384. package/apps/web/src/hooks/use-analytics.ts +122 -0
  385. package/apps/web/src/hooks/use-hub-connection.ts +587 -0
  386. package/apps/web/src/hooks/use-mobile.ts +76 -0
  387. package/apps/web/src/hooks/use-thread-history.ts +210 -0
  388. package/apps/web/src/index.css +269 -0
  389. package/apps/web/src/main.tsx +10 -0
  390. package/apps/web/src/services/hub-client.ts +358 -0
  391. package/apps/web/src/store/index.ts +528 -0
  392. package/apps/web/src/types/index.ts +119 -0
  393. package/apps/web/src/utils/account-refresh.ts +168 -0
  394. package/apps/web/src/utils/approval-policy.ts +53 -0
  395. package/apps/web/src/utils/init-prompt.ts +41 -0
  396. package/apps/web/src/utils/item-format.ts +170 -0
  397. package/apps/web/src/utils/prompt-expander.ts +62 -0
  398. package/apps/web/src/utils/reasoning-summary.ts +48 -0
  399. package/apps/web/src/utils/slash-commands.ts +98 -0
  400. package/apps/web/tsconfig.app.json +28 -0
  401. package/apps/web/tsconfig.json +7 -0
  402. package/apps/web/tsconfig.node.json +26 -0
  403. package/apps/web/vite.config.ts +8 -0
  404. package/bin/better-codex.cjs +199 -0
  405. package/package.json +20 -0
@@ -0,0 +1,531 @@
1
+ import { Elysia, t } from 'elysia'
2
+ import { cors } from '@elysiajs/cors'
3
+ import { mkdir, readdir, readFile } from 'node:fs/promises'
4
+ import { join, extname, basename } from 'node:path'
5
+ import { loadConfig } from './config'
6
+ import { CodexSupervisor } from './services/supervisor'
7
+ import { ProfileStore } from './services/profile-store'
8
+ import { AnalyticsStore } from './analytics/store'
9
+ import { AnalyticsService } from './analytics/service'
10
+ import { ThreadIndexStore } from './thread-index/store'
11
+ import { ThreadIndexService, type ThreadListItem } from './thread-index/service'
12
+ import { ReviewService } from './reviews/service'
13
+ import { ReviewStore } from './reviews/store'
14
+ import type { WsEvent, WsRequest, WsResponse } from './ws/messages'
15
+
16
+ const config = loadConfig()
17
+
18
+ const profileStore = new ProfileStore(config.dataDir, config.profilesDir)
19
+ await profileStore.init()
20
+ await profileStore.ensureDefault(config.defaultCodexHome)
21
+
22
+ const supervisor = new CodexSupervisor(config)
23
+ const analytics = new AnalyticsService(new AnalyticsStore(join(config.dataDir, 'analytics.sqlite')))
24
+ analytics.init()
25
+ const threadIndex = new ThreadIndexService(new ThreadIndexStore(join(config.dataDir, 'threads.sqlite')))
26
+ threadIndex.init()
27
+ const reviews = new ReviewService(new ReviewStore(join(config.dataDir, 'reviews.sqlite')))
28
+ reviews.init()
29
+
30
+ type WsClient = { send: (data: string) => void; id: string }
31
+
32
+ const clients = new Set<WsClient>()
33
+
34
+ const sendWsEvent = (event: WsEvent) => {
35
+ const payload = JSON.stringify(event)
36
+ clients.forEach((client) => client.send(payload))
37
+ }
38
+
39
+ const app = new Elysia()
40
+ .use(cors({ origin: true }))
41
+ .get('/health', () => ({ ok: true }))
42
+ .get(
43
+ '/analytics/daily',
44
+ ({ query }) => {
45
+ const metric = typeof query.metric === 'string' ? query.metric : 'turns_started'
46
+ const profileId = typeof query.profileId === 'string' ? query.profileId : undefined
47
+ const model = typeof query.model === 'string' ? query.model : undefined
48
+ const days = Number(query.days ?? 365)
49
+ const series = analytics.getDailySeries(metric, profileId, model, Number.isFinite(days) ? days : 365)
50
+ return { metric, series }
51
+ },
52
+ {
53
+ query: t.Object({
54
+ metric: t.Optional(t.String()),
55
+ profileId: t.Optional(t.String()),
56
+ model: t.Optional(t.String()),
57
+ days: t.Optional(t.String()),
58
+ }),
59
+ }
60
+ )
61
+ .get('/profiles', () => ({ profiles: profileStore.list() }))
62
+ .get(
63
+ '/reviews',
64
+ ({ query }) => {
65
+ const profileId = typeof query.profileId === 'string' ? query.profileId : undefined
66
+ const limit = Number(query.limit ?? 100)
67
+ const offset = Number(query.offset ?? 0)
68
+ return { sessions: reviews.list({ profileId, limit, offset }) }
69
+ },
70
+ {
71
+ query: t.Object({
72
+ profileId: t.Optional(t.String()),
73
+ limit: t.Optional(t.String()),
74
+ offset: t.Optional(t.String()),
75
+ }),
76
+ }
77
+ )
78
+ .post(
79
+ '/profiles',
80
+ async ({ body }) => {
81
+ const profile = await profileStore.create(body?.name)
82
+ return { profile }
83
+ },
84
+ {
85
+ body: t.Optional(
86
+ t.Object({
87
+ name: t.Optional(t.String()),
88
+ })
89
+ ),
90
+ }
91
+ )
92
+ .post(
93
+ '/profiles/:profileId/start',
94
+ async ({ params }) => {
95
+ const profile = profileStore.get(params.profileId)
96
+ if (!profile) {
97
+ return new Response('Profile not found', { status: 404 })
98
+ }
99
+ await supervisor.start(profile)
100
+ return { ok: true }
101
+ },
102
+ {
103
+ params: t.Object({
104
+ profileId: t.String(),
105
+ }),
106
+ }
107
+ )
108
+ .post(
109
+ '/profiles/:profileId/stop',
110
+ async ({ params }) => {
111
+ await supervisor.stop(params.profileId)
112
+ return { ok: true }
113
+ },
114
+ {
115
+ params: t.Object({
116
+ profileId: t.String(),
117
+ }),
118
+ }
119
+ )
120
+ .delete(
121
+ '/profiles/:profileId',
122
+ async ({ params }) => {
123
+ if (params.profileId === 'default') {
124
+ return new Response('Cannot remove default profile', { status: 400 })
125
+ }
126
+ await supervisor.stop(params.profileId)
127
+ const removed = await profileStore.remove(params.profileId)
128
+ if (!removed) {
129
+ return new Response('Profile not found', { status: 404 })
130
+ }
131
+ return { ok: true }
132
+ },
133
+ {
134
+ params: t.Object({
135
+ profileId: t.String(),
136
+ }),
137
+ }
138
+ )
139
+ .get(
140
+ '/profiles/:profileId/prompts',
141
+ async ({ params }) => {
142
+ const profile = profileStore.get(params.profileId)
143
+ if (!profile) {
144
+ return new Response('Profile not found', { status: 404 })
145
+ }
146
+ const promptsDir = join(profile.codexHome, 'prompts')
147
+ let entries: Array<string> = []
148
+ try {
149
+ entries = (await readdir(promptsDir)).filter((entry) => entry.endsWith('.md'))
150
+ } catch {
151
+ return { prompts: [] }
152
+ }
153
+
154
+ const prompts = await Promise.all(
155
+ entries.map(async (entry) => {
156
+ const fullPath = join(promptsDir, entry)
157
+ let description: string | undefined
158
+ try {
159
+ const raw = await readFile(fullPath, 'utf8')
160
+ const frontmatterMatch = raw.match(/^---\n([\s\S]*?)\n---/)
161
+ const frontmatterBody = frontmatterMatch?.[1]
162
+ if (frontmatterBody !== undefined) {
163
+ const lines = frontmatterBody.split('\n')
164
+ const descLine = lines.find((line) => line.trim().startsWith('description:'))
165
+ if (descLine) {
166
+ description = descLine
167
+ .split(':')
168
+ .slice(1)
169
+ .join(':')
170
+ .trim()
171
+ .replace(/^\"|\"$/g, '')
172
+ }
173
+ }
174
+ } catch {
175
+ // ignore
176
+ }
177
+ const name = basename(entry, extname(entry))
178
+ return { name, description }
179
+ })
180
+ )
181
+
182
+ return { prompts }
183
+ },
184
+ {
185
+ params: t.Object({
186
+ profileId: t.String(),
187
+ }),
188
+ }
189
+ )
190
+ .get(
191
+ '/profiles/:profileId/prompts/:name',
192
+ async ({ params }) => {
193
+ const profile = profileStore.get(params.profileId)
194
+ if (!profile) {
195
+ return new Response('Profile not found', { status: 404 })
196
+ }
197
+ if (!/^[a-zA-Z0-9._-]+$/.test(params.name)) {
198
+ return new Response('Invalid prompt name', { status: 400 })
199
+ }
200
+ const promptsDir = join(profile.codexHome, 'prompts')
201
+ const target = params.name.endsWith('.md') ? params.name : `${params.name}.md`
202
+ const fullPath = join(promptsDir, target)
203
+ try {
204
+ const content = await readFile(fullPath, 'utf8')
205
+ return { content }
206
+ } catch {
207
+ return new Response('Prompt not found', { status: 404 })
208
+ }
209
+ },
210
+ {
211
+ params: t.Object({
212
+ profileId: t.String(),
213
+ name: t.String(),
214
+ }),
215
+ }
216
+ )
217
+ .get(
218
+ '/threads/search',
219
+ ({ query }) => {
220
+ const q = typeof query.q === 'string' ? query.q : undefined
221
+ const profileId = typeof query.profileId === 'string' ? query.profileId : undefined
222
+ const model = typeof query.model === 'string' ? query.model : undefined
223
+ const status = query.status === 'archived' ? 'archived' : query.status === 'active' ? 'active' : undefined
224
+ const createdAfter = query.createdAfter ? Number(query.createdAfter) : undefined
225
+ const createdBefore = query.createdBefore ? Number(query.createdBefore) : undefined
226
+ const limit = query.limit ? Number(query.limit) : undefined
227
+ const offset = query.offset ? Number(query.offset) : undefined
228
+ const threads = threadIndex.search({
229
+ query: q,
230
+ profileId,
231
+ model,
232
+ status,
233
+ createdAfter: Number.isFinite(createdAfter) ? createdAfter : undefined,
234
+ createdBefore: Number.isFinite(createdBefore) ? createdBefore : undefined,
235
+ limit: Number.isFinite(limit) ? limit : undefined,
236
+ offset: Number.isFinite(offset) ? offset : undefined,
237
+ })
238
+ return { threads }
239
+ },
240
+ {
241
+ query: t.Object({
242
+ q: t.Optional(t.String()),
243
+ profileId: t.Optional(t.String()),
244
+ model: t.Optional(t.String()),
245
+ status: t.Optional(t.String()),
246
+ createdAfter: t.Optional(t.String()),
247
+ createdBefore: t.Optional(t.String()),
248
+ limit: t.Optional(t.String()),
249
+ offset: t.Optional(t.String()),
250
+ }),
251
+ }
252
+ )
253
+ .post(
254
+ '/threads/reindex',
255
+ async ({ body }) => {
256
+ const limit = typeof body?.limit === 'number' ? body.limit : 100
257
+ const autoStart = body?.autoStart === true
258
+ const targetProfileId = typeof body?.profileId === 'string' ? body.profileId : null
259
+ const profiles = targetProfileId
260
+ ? profileStore.list().filter((profile) => profile.id === targetProfileId)
261
+ : profileStore.list()
262
+
263
+ const results: Array<{ profileId: string; ok: boolean; error?: string }> = []
264
+ for (const profile of profiles) {
265
+ try {
266
+ if (autoStart) {
267
+ await supervisor.start(profile)
268
+ }
269
+ const response = await supervisor.request(profile.id, 'thread/list', { limit })
270
+ const data = (response as { data?: ThreadListItem[] }).data ?? []
271
+ threadIndex.recordThreadList(profile.id, data)
272
+ results.push({ profileId: profile.id, ok: true })
273
+ } catch (error) {
274
+ results.push({ profileId: profile.id, ok: false, error: error instanceof Error ? error.message : 'failed' })
275
+ }
276
+ }
277
+
278
+ return { ok: true, results }
279
+ },
280
+ {
281
+ body: t.Optional(
282
+ t.Object({
283
+ profileId: t.Optional(t.String()),
284
+ limit: t.Optional(t.Number()),
285
+ autoStart: t.Optional(t.Boolean()),
286
+ })
287
+ ),
288
+ }
289
+ )
290
+ .ws('/ws', {
291
+ open(ws) {
292
+ // Get token from query params via ws.data which contains the request context
293
+ const token = ws.data.query?.token as string | undefined
294
+ if (token !== config.authToken) {
295
+ ws.close(1008, 'unauthorized')
296
+ return
297
+ }
298
+ clients.add(ws)
299
+ },
300
+ close(ws) {
301
+ clients.delete(ws)
302
+ },
303
+ async message(ws, message) {
304
+ let payload: WsRequest | null = null
305
+ try {
306
+ if (typeof message === 'string') {
307
+ payload = JSON.parse(message) as WsRequest
308
+ } else if (message instanceof ArrayBuffer) {
309
+ const text = new TextDecoder().decode(message)
310
+ payload = JSON.parse(text) as WsRequest
311
+ } else if (ArrayBuffer.isView(message)) {
312
+ const view = new Uint8Array(
313
+ message.buffer,
314
+ message.byteOffset,
315
+ message.byteLength
316
+ )
317
+ const text = new TextDecoder().decode(view)
318
+ payload = JSON.parse(text) as WsRequest
319
+ } else if (message && typeof message === 'object') {
320
+ payload = message as WsRequest
321
+ }
322
+ } catch {
323
+ const response: WsResponse = { type: 'error', message: 'Invalid JSON' }
324
+ ws.send(JSON.stringify(response))
325
+ return
326
+ }
327
+
328
+ if (!payload) {
329
+ return
330
+ }
331
+
332
+ if (payload.type === 'profile.start') {
333
+ const profile = profileStore.get(payload.profileId)
334
+ if (!profile) {
335
+ const response: WsResponse = {
336
+ type: 'error',
337
+ message: 'Profile not found',
338
+ }
339
+ ws.send(JSON.stringify(response))
340
+ return
341
+ }
342
+ await supervisor.start(profile)
343
+ const response: WsResponse = {
344
+ type: 'profile.started',
345
+ profileId: profile.id,
346
+ }
347
+ ws.send(JSON.stringify(response))
348
+ return
349
+ }
350
+
351
+ if (payload.type === 'profile.stop') {
352
+ await supervisor.stop(payload.profileId)
353
+ const response: WsResponse = {
354
+ type: 'profile.stopped',
355
+ profileId: payload.profileId,
356
+ }
357
+ ws.send(JSON.stringify(response))
358
+ return
359
+ }
360
+
361
+ if (payload.type === 'rpc.request') {
362
+ try {
363
+ analytics.trackRpcRequest({
364
+ profileId: payload.profileId,
365
+ method: payload.method,
366
+ params: payload.params,
367
+ })
368
+ const result = await supervisor.request(
369
+ payload.profileId,
370
+ payload.method,
371
+ payload.params
372
+ )
373
+ if (payload.method === 'thread/list') {
374
+ const data = (result as { data?: ThreadListItem[] }).data ?? []
375
+ threadIndex.recordThreadList(payload.profileId, data)
376
+ }
377
+ if (payload.method === 'thread/start') {
378
+ const thread = (result as { thread?: ThreadListItem }).thread
379
+ threadIndex.recordThreadStart(payload.profileId, thread)
380
+ }
381
+ if (payload.method === 'thread/resume') {
382
+ const thread = (result as { thread?: ThreadListItem }).thread
383
+ threadIndex.recordThreadResume(payload.profileId, thread)
384
+ }
385
+ if (payload.method === 'thread/archive') {
386
+ const params = payload.params as { threadId?: string } | undefined
387
+ if (params?.threadId) {
388
+ threadIndex.recordThreadArchive(payload.profileId, params.threadId)
389
+ }
390
+ }
391
+ analytics.trackRpcResponse({
392
+ profileId: payload.profileId,
393
+ method: payload.method,
394
+ result,
395
+ })
396
+ const response: WsResponse = {
397
+ type: 'rpc.response',
398
+ requestId: payload.requestId,
399
+ result,
400
+ }
401
+ ws.send(JSON.stringify(response))
402
+ } catch (error) {
403
+ analytics.trackRpcResponse({
404
+ profileId: payload.profileId,
405
+ method: payload.method,
406
+ error: error instanceof Error ? error.message : 'Request failed',
407
+ })
408
+ const response: WsResponse = {
409
+ type: 'rpc.response',
410
+ requestId: payload.requestId,
411
+ error: error instanceof Error ? error.message : 'Request failed',
412
+ }
413
+ ws.send(JSON.stringify(response))
414
+ }
415
+ return
416
+ }
417
+
418
+ if (payload.type === 'rpc.response') {
419
+ analytics.trackApprovalDecision(payload.profileId, payload.id, payload.result, payload.error ?? null)
420
+ supervisor.respond(payload.profileId, payload.id, payload.result, payload.error)
421
+ }
422
+ },
423
+ })
424
+
425
+ supervisor.on('notification', (event) => {
426
+ if (event.method === 'item/started' && event.params && typeof event.params === 'object') {
427
+ const { threadId, turnId, item } = event.params as {
428
+ threadId?: string
429
+ turnId?: string
430
+ item?: { id?: string; type?: string; review?: string }
431
+ }
432
+ if (threadId && item?.type === 'enteredReviewMode') {
433
+ const sessionId = turnId ?? item.id ?? `${threadId}-${Date.now()}`
434
+ reviews.upsert({
435
+ id: sessionId,
436
+ threadId,
437
+ profileId: event.profileId,
438
+ label: typeof item.review === 'string' ? item.review : 'Review',
439
+ status: 'running',
440
+ startedAt: Date.now(),
441
+ completedAt: null,
442
+ model: null,
443
+ cwd: null,
444
+ review: null,
445
+ })
446
+ }
447
+ }
448
+ if (event.method === 'item/completed' && event.params && typeof event.params === 'object') {
449
+ const { turnId, item } = event.params as {
450
+ turnId?: string
451
+ item?: { id?: string; type?: string; review?: string }
452
+ }
453
+ if (item?.type === 'exitedReviewMode') {
454
+ const sessionId = turnId ?? item.id
455
+ if (sessionId) {
456
+ reviews.complete(sessionId, typeof item.review === 'string' ? item.review : null, Date.now())
457
+ }
458
+ }
459
+ }
460
+ if (event.method === 'thread/started') {
461
+ const thread = (event.params as { thread?: ThreadListItem })?.thread
462
+ threadIndex.recordThreadStart(event.profileId, thread)
463
+ }
464
+ analytics.trackRpcEvent({
465
+ profileId: event.profileId,
466
+ method: event.method,
467
+ params: event.params,
468
+ })
469
+ const wsEvent: WsEvent = {
470
+ type: 'rpc.event',
471
+ profileId: event.profileId,
472
+ method: event.method,
473
+ params: event.params,
474
+ }
475
+ sendWsEvent(wsEvent)
476
+ })
477
+
478
+ supervisor.on('serverRequest', (event) => {
479
+ analytics.trackServerRequest({
480
+ profileId: event.profileId,
481
+ id: event.id,
482
+ method: event.method,
483
+ params: event.params,
484
+ })
485
+ const wsEvent: WsEvent = {
486
+ type: 'rpc.serverRequest',
487
+ profileId: event.profileId,
488
+ id: event.id,
489
+ method: event.method,
490
+ params: event.params,
491
+ }
492
+ sendWsEvent(wsEvent)
493
+ })
494
+
495
+ supervisor.on('diagnostic', (event) => {
496
+ const wsEvent: WsEvent = {
497
+ type: 'profile.diagnostic',
498
+ profileId: event.profileId,
499
+ message: event.message,
500
+ }
501
+ sendWsEvent(wsEvent)
502
+ })
503
+
504
+ supervisor.on('exit', (event) => {
505
+ const wsEvent: WsEvent = {
506
+ type: 'profile.exit',
507
+ profileId: event.profileId,
508
+ code: event.code,
509
+ }
510
+ sendWsEvent(wsEvent)
511
+ })
512
+
513
+ supervisor.on('error', (event) => {
514
+ const wsEvent: WsEvent = {
515
+ type: 'profile.error',
516
+ profileId: event.profileId,
517
+ message: event.error.message,
518
+ }
519
+ sendWsEvent(wsEvent)
520
+ })
521
+
522
+ await mkdir(config.dataDir, { recursive: true })
523
+ app.listen({
524
+ port: config.port,
525
+ hostname: config.host,
526
+ })
527
+
528
+ console.log(
529
+ `Codex Hub backend listening on http://${config.host}:${config.port}`
530
+ )
531
+ console.log(`WebSocket token: ${config.authToken}`)
@@ -0,0 +1,114 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises'
3
+ import { dirname, join } from 'node:path'
4
+
5
+ export type Profile = {
6
+ id: string
7
+ name: string
8
+ codexHome: string
9
+ createdAt: string
10
+ }
11
+
12
+ type ProfileStoreState = {
13
+ profiles: Profile[]
14
+ }
15
+
16
+ export class ProfileStore {
17
+ private readonly metadataPath: string
18
+ private state: ProfileStoreState = { profiles: [] }
19
+
20
+ constructor(
21
+ private readonly dataDir: string,
22
+ private readonly profilesDir: string
23
+ ) {
24
+ this.metadataPath = join(this.dataDir, 'profiles.json')
25
+ }
26
+
27
+ async init(): Promise<void> {
28
+ await mkdir(this.dataDir, { recursive: true })
29
+ await mkdir(this.profilesDir, { recursive: true })
30
+ try {
31
+ const raw = await readFile(this.metadataPath, 'utf8')
32
+ const parsed = JSON.parse(raw) as ProfileStoreState
33
+ this.state = {
34
+ profiles: Array.isArray(parsed.profiles) ? parsed.profiles : [],
35
+ }
36
+ } catch {
37
+ await this.persist()
38
+ }
39
+ }
40
+
41
+ list(): Profile[] {
42
+ return [...this.state.profiles]
43
+ }
44
+
45
+ get(profileId: string): Profile | undefined {
46
+ return this.state.profiles.find((profile) => profile.id === profileId)
47
+ }
48
+
49
+ async create(name?: string): Promise<Profile> {
50
+ const id = randomUUID()
51
+ const codexHome = join(this.profilesDir, id)
52
+ await mkdir(codexHome, { recursive: true })
53
+
54
+ const profile: Profile = {
55
+ id,
56
+ name: name?.trim() || `Profile ${this.state.profiles.length + 1}`,
57
+ codexHome,
58
+ createdAt: new Date().toISOString(),
59
+ }
60
+
61
+ this.state.profiles.push(profile)
62
+ await this.persist()
63
+ return profile
64
+ }
65
+
66
+ async ensureDefault(defaultCodexHome: string): Promise<Profile> {
67
+ const existing = this.state.profiles.find(
68
+ (profile) => profile.codexHome === defaultCodexHome
69
+ )
70
+ if (existing) {
71
+ return existing
72
+ }
73
+
74
+ const id = this.state.profiles.some((profile) => profile.id === 'default')
75
+ ? randomUUID()
76
+ : 'default'
77
+
78
+ const profile: Profile = {
79
+ id,
80
+ name: 'Default',
81
+ codexHome: defaultCodexHome,
82
+ createdAt: new Date().toISOString(),
83
+ }
84
+
85
+ this.state.profiles.unshift(profile)
86
+ await this.persist()
87
+ return profile
88
+ }
89
+
90
+ async rename(profileId: string, name: string): Promise<Profile | undefined> {
91
+ const profile = this.get(profileId)
92
+ if (!profile) {
93
+ return undefined
94
+ }
95
+ profile.name = name
96
+ await this.persist()
97
+ return profile
98
+ }
99
+
100
+ async remove(profileId: string): Promise<boolean> {
101
+ const next = this.state.profiles.filter((profile) => profile.id !== profileId)
102
+ if (next.length === this.state.profiles.length) {
103
+ return false
104
+ }
105
+ this.state.profiles = next
106
+ await this.persist()
107
+ return true
108
+ }
109
+
110
+ private async persist(): Promise<void> {
111
+ await mkdir(dirname(this.metadataPath), { recursive: true })
112
+ await writeFile(this.metadataPath, JSON.stringify(this.state, null, 2))
113
+ }
114
+ }