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,748 @@
1
+ import { useRef, useEffect, useState, useCallback, useMemo } from 'react'
2
+ import { useVirtualizer } from '@tanstack/react-virtual'
3
+ import type { Message, ApprovalRequest, ThreadStatus, QueuedMessage } from '../../types'
4
+ import { Avatar, Button, Icons, CollapsibleContent, ThinkingIndicator } from '../ui'
5
+ import { Markdown } from '../ui'
6
+
7
+ interface VirtualizedMessageListProps {
8
+ messages: Message[]
9
+ approvals: ApprovalRequest[]
10
+ queuedMessages: QueuedMessage[]
11
+ threadStatus?: ThreadStatus
12
+ turnStartedAt?: number
13
+ lastTurnDuration?: number
14
+ onApprove: (approval: ApprovalRequest) => void
15
+ onApproveForSession?: (approval: ApprovalRequest) => void
16
+ onDeny: (approval: ApprovalRequest) => void
17
+ onInterrupt?: () => void
18
+ }
19
+
20
+ interface Turn {
21
+ id: string
22
+ userMessage?: Message
23
+ assistantActions: AssistantAction[]
24
+ timestamp: string
25
+ }
26
+
27
+ interface AssistantAction {
28
+ type: 'chat' | 'reasoning' | 'explored' | 'edited' | 'ran' | 'searched'
29
+ messages: Message[]
30
+ label: string
31
+ summary?: string
32
+ }
33
+
34
+ function getActionType(msg: Message): AssistantAction['type'] {
35
+ if (msg.kind === 'reasoning') return 'reasoning'
36
+ if (msg.kind === 'file') {
37
+ const title = msg.title?.toLowerCase() ?? ''
38
+ if (title.includes('edit') || title.includes('wrote') || title.includes('creat')) return 'edited'
39
+ return 'explored'
40
+ }
41
+ if (msg.kind === 'command') return 'ran'
42
+ if (msg.kind === 'tool') {
43
+ const title = msg.title?.toLowerCase() ?? ''
44
+ if (title.includes('web search')) return 'searched'
45
+ if (title.includes('read') || title.includes('view') || title.includes('list')) return 'explored'
46
+ if (title.includes('edit') || title.includes('wrote') || title.includes('creat')) return 'edited'
47
+ if (title.includes('ran') || title.includes('exec') || title.includes('command')) return 'ran'
48
+ return 'explored'
49
+ }
50
+ return 'chat'
51
+ }
52
+
53
+ function getActionLabel(type: AssistantAction['type'], messages: Message[]): { label: string; summary?: string } {
54
+ const count = messages.length
55
+
56
+ switch (type) {
57
+ case 'reasoning': {
58
+ const lines = messages.reduce((acc, m) => acc + m.content.split('\n').length, 0)
59
+ return { label: 'Reasoning', summary: `${lines} lines` }
60
+ }
61
+ case 'explored': {
62
+ const files = messages.map(m => {
63
+ const title = m.title ?? ''
64
+ const match = title.match(/(?:read|view|search|list)[:\s]+(.+)/i)
65
+ if (match) return match[1].trim()
66
+ const firstLine = m.content.split('\n')[0]
67
+ if (firstLine.length < 60) return firstLine
68
+ return title || 'file'
69
+ })
70
+
71
+ if (count === 1) {
72
+ return { label: 'Read', summary: files[0] }
73
+ }
74
+ if (files.length <= 3) {
75
+ return { label: 'Read', summary: files.join(', ') }
76
+ }
77
+ return { label: 'Read', summary: `${count} files` }
78
+ }
79
+ case 'edited': {
80
+ const files = messages.map(m => {
81
+ const title = m.title ?? ''
82
+ const match = title.match(/(?:edit|wrote|creat)[:\s]+(.+)/i)
83
+ if (match) return match[1].trim()
84
+ return title || 'file'
85
+ })
86
+
87
+ if (count === 1) {
88
+ return { label: 'Edited', summary: files[0] }
89
+ }
90
+ return { label: 'Edited', summary: `${count} files` }
91
+ }
92
+ case 'ran': {
93
+ if (count === 1) {
94
+ const cmd = messages[0].title ?? messages[0].content.split('\n')[0]
95
+ const shortCmd = cmd.length > 40 ? cmd.slice(0, 40) + '...' : cmd
96
+ return { label: 'Ran', summary: shortCmd }
97
+ }
98
+ return { label: 'Ran', summary: `${count} commands` }
99
+ }
100
+ case 'searched': {
101
+ if (count === 1) {
102
+ const query = messages[0].content.trim()
103
+ const shortQuery = query.length > 50 ? query.slice(0, 50) + '...' : query
104
+ return { label: 'Searched', summary: shortQuery }
105
+ }
106
+ return { label: 'Searched', summary: `${count} queries` }
107
+ }
108
+ default:
109
+ return { label: 'Response' }
110
+ }
111
+ }
112
+
113
+ function groupMessagesIntoTurns(messages: Message[]): Turn[] {
114
+ const turns: Turn[] = []
115
+ let currentTurn: Turn | null = null
116
+ let pendingActions: Message[] = []
117
+ let lastActionType: AssistantAction['type'] | null = null
118
+
119
+ const flushPendingActions = () => {
120
+ if (pendingActions.length > 0 && currentTurn && lastActionType) {
121
+ const { label, summary } = getActionLabel(lastActionType, pendingActions)
122
+ currentTurn.assistantActions.push({
123
+ type: lastActionType,
124
+ messages: [...pendingActions],
125
+ label,
126
+ summary,
127
+ })
128
+ pendingActions = []
129
+ lastActionType = null
130
+ }
131
+ }
132
+
133
+ for (const msg of messages) {
134
+ if (msg.role === 'user') {
135
+ flushPendingActions()
136
+
137
+ currentTurn = {
138
+ id: msg.id,
139
+ userMessage: msg,
140
+ assistantActions: [],
141
+ timestamp: msg.timestamp,
142
+ }
143
+ turns.push(currentTurn)
144
+ } else {
145
+ if (!currentTurn) {
146
+ currentTurn = {
147
+ id: msg.id,
148
+ assistantActions: [],
149
+ timestamp: msg.timestamp,
150
+ }
151
+ turns.push(currentTurn)
152
+ }
153
+
154
+ const actionType = getActionType(msg)
155
+
156
+ if (actionType === 'chat') {
157
+ flushPendingActions()
158
+ const { label, summary } = getActionLabel(actionType, [msg])
159
+ currentTurn.assistantActions.push({
160
+ type: actionType,
161
+ messages: [msg],
162
+ label,
163
+ summary,
164
+ })
165
+ }
166
+ else if (actionType === 'reasoning') {
167
+ if (lastActionType === 'reasoning') {
168
+ pendingActions.push(msg)
169
+ } else {
170
+ flushPendingActions()
171
+ pendingActions = [msg]
172
+ lastActionType = 'reasoning'
173
+ }
174
+ }
175
+ else if (actionType === 'explored') {
176
+ if (lastActionType === 'explored') {
177
+ pendingActions.push(msg)
178
+ } else {
179
+ flushPendingActions()
180
+ pendingActions = [msg]
181
+ lastActionType = 'explored'
182
+ }
183
+ }
184
+ else if (actionType === 'edited') {
185
+ if (lastActionType === 'edited') {
186
+ pendingActions.push(msg)
187
+ } else {
188
+ flushPendingActions()
189
+ pendingActions = [msg]
190
+ lastActionType = 'edited'
191
+ }
192
+ }
193
+ else if (actionType === 'ran') {
194
+ if (lastActionType === 'ran') {
195
+ pendingActions.push(msg)
196
+ } else {
197
+ flushPendingActions()
198
+ pendingActions = [msg]
199
+ lastActionType = 'ran'
200
+ }
201
+ }
202
+ // Web search actions are grouped
203
+ else if (actionType === 'searched') {
204
+ if (lastActionType === 'searched') {
205
+ pendingActions.push(msg)
206
+ } else {
207
+ flushPendingActions()
208
+ pendingActions = [msg]
209
+ lastActionType = 'searched'
210
+ }
211
+ }
212
+ }
213
+ }
214
+
215
+ flushPendingActions()
216
+
217
+ return turns
218
+ }
219
+
220
+ export function VirtualizedMessageList({
221
+ messages,
222
+ approvals,
223
+ queuedMessages,
224
+ threadStatus,
225
+ turnStartedAt,
226
+ lastTurnDuration,
227
+ onApprove,
228
+ onApproveForSession,
229
+ onDeny,
230
+ onInterrupt
231
+ }: VirtualizedMessageListProps) {
232
+ const parentRef = useRef<HTMLDivElement>(null)
233
+ const [userHasScrolled, setUserHasScrolled] = useState(false)
234
+ const lastScrollTop = useRef(0)
235
+ const isAutoScrolling = useRef(false)
236
+ const prevItemsLength = useRef(0)
237
+
238
+ const lastMessage = messages[messages.length - 1]
239
+ const isWaitingForResponse = threadStatus === 'active' && lastMessage?.role === 'user'
240
+ const isTaskRunning = threadStatus === 'active'
241
+
242
+ const turns = useMemo(() => groupMessagesIntoTurns(messages), [messages])
243
+
244
+ const items: Array<
245
+ | { type: 'turn'; data: Turn }
246
+ | { type: 'approval'; data: ApprovalRequest }
247
+ | { type: 'thinking'; data: null }
248
+ | { type: 'working'; data: { startedAt: number } }
249
+ | { type: 'worked'; data: { duration: number } }
250
+ | { type: 'queued'; data: QueuedMessage }
251
+ > = [
252
+ ...turns.map(t => ({ type: 'turn' as const, data: t })),
253
+ ...approvals.map(a => ({ type: 'approval' as const, data: a })),
254
+ ...(isWaitingForResponse ? [{ type: 'thinking' as const, data: null }] : []),
255
+ ...(isTaskRunning && !isWaitingForResponse && turnStartedAt ? [{ type: 'working' as const, data: { startedAt: turnStartedAt } }] : []),
256
+ ...(!isTaskRunning && lastTurnDuration ? [{ type: 'worked' as const, data: { duration: lastTurnDuration } }] : []),
257
+ ...queuedMessages.map(q => ({ type: 'queued' as const, data: q })),
258
+ ]
259
+
260
+ const initialScrollDone = useRef(false)
261
+
262
+ const virtualizer = useVirtualizer({
263
+ count: items.length,
264
+ getScrollElement: () => parentRef.current,
265
+ estimateSize: (index) => {
266
+ const item = items[index]
267
+ if (item.type === 'approval') return 140
268
+ if (item.type === 'thinking') return 60
269
+ if (item.type === 'working') return 60
270
+ if (item.type === 'worked') return 40
271
+ if (item.type === 'queued') return 80
272
+ const turn = item.data as Turn
273
+ const userHeight = turn.userMessage ? 80 : 0
274
+ const actionsHeight = turn.assistantActions.reduce((acc, action) => {
275
+ if (action.type === 'chat') {
276
+ const contentLength = action.messages[0]?.content.length ?? 0
277
+ return acc + Math.max(60, Math.min(300, 40 + contentLength * 0.2))
278
+ }
279
+ return acc + 44
280
+ }, 0)
281
+ return userHeight + actionsHeight + 16
282
+ },
283
+ overscan: 3,
284
+ })
285
+
286
+ const handleScroll = useCallback(() => {
287
+ if (!parentRef.current || isAutoScrolling.current) return
288
+
289
+ const { scrollTop, scrollHeight, clientHeight } = parentRef.current
290
+ const isAtBottom = scrollHeight - scrollTop - clientHeight < 50
291
+
292
+ if (scrollTop < lastScrollTop.current && !isAtBottom) {
293
+ setUserHasScrolled(true)
294
+ }
295
+
296
+ if (isAtBottom) {
297
+ setUserHasScrolled(false)
298
+ }
299
+
300
+ lastScrollTop.current = scrollTop
301
+ }, [])
302
+
303
+ useEffect(() => {
304
+ if (items.length > 0 && !initialScrollDone.current) {
305
+ initialScrollDone.current = true
306
+ requestAnimationFrame(() => {
307
+ virtualizer.scrollToIndex(items.length - 1, { align: 'end' })
308
+ })
309
+ }
310
+ }, [items.length, virtualizer])
311
+
312
+ useEffect(() => {
313
+ const hasNewItems = items.length > prevItemsLength.current
314
+ prevItemsLength.current = items.length
315
+
316
+ if (items.length > 0 && hasNewItems && !userHasScrolled && initialScrollDone.current) {
317
+ isAutoScrolling.current = true
318
+ virtualizer.scrollToIndex(items.length - 1, { align: 'end', behavior: 'smooth' })
319
+ setTimeout(() => {
320
+ isAutoScrolling.current = false
321
+ }, 500)
322
+ }
323
+ }, [items.length, virtualizer, userHasScrolled])
324
+
325
+ const scrollToBottom = useCallback(() => {
326
+ if (items.length > 0) {
327
+ setUserHasScrolled(false)
328
+ isAutoScrolling.current = true
329
+ virtualizer.scrollToIndex(items.length - 1, { align: 'end', behavior: 'smooth' })
330
+ setTimeout(() => {
331
+ isAutoScrolling.current = false
332
+ }, 500)
333
+ }
334
+ }, [items.length, virtualizer])
335
+
336
+ if (items.length === 0) {
337
+ return (
338
+ <div className="flex-1 flex items-center justify-center">
339
+ <p className="text-xs text-text-muted">No messages yet. Start the conversation!</p>
340
+ </div>
341
+ )
342
+ }
343
+
344
+ return (
345
+ <div className="flex-1 relative">
346
+ <div
347
+ ref={parentRef}
348
+ className="h-full overflow-y-auto px-3 md:px-6 touch-scroll"
349
+ style={{ contain: 'strict' }}
350
+ onScroll={handleScroll}
351
+ >
352
+ <div
353
+ className="max-w-4xl mx-auto relative"
354
+ style={{ height: `${virtualizer.getTotalSize()}px` }}
355
+ >
356
+ {virtualizer.getVirtualItems().map((virtualItem) => {
357
+ const item = items[virtualItem.index]
358
+
359
+ return (
360
+ <div
361
+ key={virtualItem.key}
362
+ className="absolute top-0 left-0 w-full py-2"
363
+ style={{
364
+ transform: `translateY(${virtualItem.start}px)`,
365
+ }}
366
+ ref={virtualizer.measureElement}
367
+ data-index={virtualItem.index}
368
+ >
369
+ {item.type === 'turn' ? (
370
+ <TurnView turn={item.data} />
371
+ ) : item.type === 'thinking' ? (
372
+ <ThinkingBubble onInterrupt={isTaskRunning ? onInterrupt : undefined} />
373
+ ) : item.type === 'working' ? (
374
+ <WorkingBubble startedAt={item.data.startedAt} onInterrupt={isTaskRunning ? onInterrupt : undefined} />
375
+ ) : item.type === 'worked' ? (
376
+ <WorkedBubble duration={item.data.duration} />
377
+ ) : item.type === 'queued' ? (
378
+ <QueuedMessageBubble message={item.data} />
379
+ ) : (
380
+ <ApprovalCard
381
+ approval={item.data as ApprovalRequest}
382
+ onApprove={() => onApprove(item.data as ApprovalRequest)}
383
+ onApproveForSession={onApproveForSession ? () => onApproveForSession(item.data as ApprovalRequest) : undefined}
384
+ onDeny={() => onDeny(item.data as ApprovalRequest)}
385
+ />
386
+ )}
387
+ </div>
388
+ )
389
+ })}
390
+ </div>
391
+ </div>
392
+
393
+ {userHasScrolled && (
394
+ <button
395
+ onClick={scrollToBottom}
396
+ className="absolute bottom-4 left-1/2 -translate-x-1/2 flex items-center gap-1.5 px-3 py-1.5
397
+ bg-bg-elevated border border-border rounded-full text-xs text-text-muted
398
+ hover:text-text-primary hover:border-text-muted transition-all shadow-lg
399
+ animate-in fade-in slide-in-from-bottom-2 duration-200"
400
+ >
401
+ <Icons.ChevronDown className="w-3 h-3" />
402
+ <span>New messages</span>
403
+ </button>
404
+ )}
405
+ </div>
406
+ )
407
+ }
408
+
409
+ function ThinkingBubble({ onInterrupt }: { onInterrupt?: () => void }) {
410
+ return (
411
+ <div className="pl-10">
412
+ <div className="flex items-center gap-2 py-2">
413
+ <span className="text-text-muted">•</span>
414
+ <ThinkingIndicator message="Thinking" />
415
+ {onInterrupt && (
416
+ <button
417
+ onClick={onInterrupt}
418
+ className="ml-2 px-2 py-0.5 text-xs text-text-muted hover:text-error hover:bg-error/10 rounded transition-colors"
419
+ >
420
+ Stop
421
+ </button>
422
+ )}
423
+ </div>
424
+ </div>
425
+ )
426
+ }
427
+
428
+ function WorkingBubble({ startedAt, onInterrupt }: { startedAt: number; onInterrupt?: () => void }) {
429
+ const [elapsed, setElapsed] = useState(() => Math.floor((Date.now() - startedAt) / 1000))
430
+
431
+ useEffect(() => {
432
+ const interval = setInterval(() => {
433
+ setElapsed(Math.floor((Date.now() - startedAt) / 1000))
434
+ }, 1000)
435
+ return () => clearInterval(interval)
436
+ }, [startedAt])
437
+
438
+ return (
439
+ <div className="pl-10">
440
+ <div className="flex items-center gap-2 py-2">
441
+ <span className="text-text-muted">•</span>
442
+ <ThinkingIndicator message="Working" elapsed={elapsed} />
443
+ {onInterrupt && (
444
+ <button
445
+ onClick={onInterrupt}
446
+ className="ml-2 px-2 py-0.5 text-xs text-text-muted hover:text-error hover:bg-error/10 rounded transition-colors"
447
+ >
448
+ Stop
449
+ </button>
450
+ )}
451
+ </div>
452
+ </div>
453
+ )
454
+ }
455
+
456
+ function WorkedBubble({ duration }: { duration: number }) {
457
+ const formatDuration = (seconds: number) => {
458
+ if (seconds < 60) return `${seconds}s`
459
+ const mins = Math.floor(seconds / 60)
460
+ const secs = seconds % 60
461
+ if (mins < 60) return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`
462
+ const hours = Math.floor(mins / 60)
463
+ const remainingMins = mins % 60
464
+ return remainingMins > 0 ? `${hours}h ${remainingMins}m` : `${hours}h`
465
+ }
466
+
467
+ return (
468
+ <div className="pl-10">
469
+ <div className="flex items-center gap-2 py-1">
470
+ <span className="text-success">✓</span>
471
+ <span className="text-xs text-text-muted">Worked for {formatDuration(duration)}</span>
472
+ </div>
473
+ </div>
474
+ )
475
+ }
476
+
477
+ function QueuedMessageBubble({ message }: { message: QueuedMessage }) {
478
+ return (
479
+ <div className="flex gap-3 justify-end opacity-50">
480
+ <div className="max-w-[75%] min-w-0">
481
+ <div className="bg-bg-elevated/50 rounded-2xl rounded-br-md px-4 py-3 border border-dashed border-border">
482
+ <p className="text-sm text-text-muted whitespace-pre-wrap leading-relaxed break-words">
483
+ {message.text}
484
+ </p>
485
+ </div>
486
+ <span className="text-[10px] text-text-muted/60 mt-1 px-1 block text-right">Queued</span>
487
+ </div>
488
+ <div className="shrink-0 mt-0.5 opacity-50">
489
+ <Avatar name="You" size="sm" />
490
+ </div>
491
+ </div>
492
+ )
493
+ }
494
+
495
+ function TurnView({ turn }: { turn: Turn }) {
496
+ return (
497
+ <div className="space-y-2">
498
+ {turn.userMessage && (
499
+ <UserMessage message={turn.userMessage} />
500
+ )}
501
+
502
+ {turn.assistantActions.length > 0 && (
503
+ <div className="pl-10 space-y-1">
504
+ {turn.assistantActions.map((action, i) => (
505
+ <ActionRow key={i} action={action} />
506
+ ))}
507
+ </div>
508
+ )}
509
+ </div>
510
+ )
511
+ }
512
+
513
+ function UserMessage({ message }: { message: Message }) {
514
+ const lineCount = message.content.split('\n').length
515
+ const charCount = message.content.length
516
+ const isSuperLong = charCount > 3000 || lineCount > 50
517
+ const [isExpanded, setIsExpanded] = useState(false)
518
+
519
+ if (isSuperLong) {
520
+ return (
521
+ <div className="flex gap-3 justify-end">
522
+ <div className="max-w-[75%] min-w-0">
523
+ <div className="bg-bg-elevated rounded-2xl rounded-br-md overflow-hidden">
524
+ <button
525
+ onClick={() => setIsExpanded(!isExpanded)}
526
+ className="w-full px-4 py-2.5 flex items-center gap-2 hover:bg-bg-hover/50 transition-colors"
527
+ >
528
+ <Icons.ChevronDown
529
+ className={`w-3.5 h-3.5 text-text-muted transition-transform duration-200
530
+ ${isExpanded ? 'rotate-180' : '-rotate-90'}`}
531
+ />
532
+ <span className="text-xs text-text-muted">Long message</span>
533
+ <span className="text-[10px] text-text-muted/60 ml-auto">
534
+ {lineCount} lines • {charCount > 10000 ? `${Math.round(charCount/1000)}k` : charCount} chars
535
+ </span>
536
+ </button>
537
+ {isExpanded && (
538
+ <div className="px-4 pb-3 max-h-[50vh] overflow-y-auto border-t border-border/50">
539
+ <p className="text-sm text-text-primary whitespace-pre-wrap leading-relaxed break-words font-mono text-xs pt-2">
540
+ {message.content}
541
+ </p>
542
+ </div>
543
+ )}
544
+ {!isExpanded && (
545
+ <div className="px-4 pb-3">
546
+ <p className="text-sm text-text-primary whitespace-pre-wrap leading-relaxed break-words line-clamp-3">
547
+ {message.content.slice(0, 200)}...
548
+ </p>
549
+ </div>
550
+ )}
551
+ </div>
552
+ <span className="text-[10px] text-text-muted mt-1 px-1 block text-right">{message.timestamp}</span>
553
+ </div>
554
+ <div className="shrink-0 mt-0.5">
555
+ <Avatar name="You" size="sm" />
556
+ </div>
557
+ </div>
558
+ )
559
+ }
560
+
561
+ return (
562
+ <div className="flex gap-3 justify-end">
563
+ <div className="max-w-[75%] min-w-0">
564
+ <div className="bg-bg-elevated rounded-2xl rounded-br-md px-4 py-3">
565
+ <p className="text-sm text-text-primary whitespace-pre-wrap leading-relaxed break-words">
566
+ {message.content}
567
+ </p>
568
+ </div>
569
+ <span className="text-[10px] text-text-muted mt-1 px-1 block text-right">{message.timestamp}</span>
570
+ </div>
571
+ <div className="shrink-0 mt-0.5">
572
+ <Avatar name="You" size="sm" />
573
+ </div>
574
+ </div>
575
+ )
576
+ }
577
+
578
+ function ActionRow({ action }: { action: AssistantAction }) {
579
+ const [isExpanded, setIsExpanded] = useState(false)
580
+
581
+ if (action.type === 'chat') {
582
+ const content = action.messages.map(m => m.content).join('\n\n')
583
+ const isLong = content.length > 1500
584
+
585
+ return (
586
+ <div className="py-2">
587
+ {isLong ? (
588
+ <CollapsibleContent maxHeight={300}>
589
+ <Markdown
590
+ content={content}
591
+ className="text-sm text-text-primary leading-relaxed"
592
+ />
593
+ </CollapsibleContent>
594
+ ) : (
595
+ <Markdown
596
+ content={content}
597
+ className="text-sm text-text-primary leading-relaxed"
598
+ />
599
+ )}
600
+ </div>
601
+ )
602
+ }
603
+
604
+ if (action.type === 'reasoning') {
605
+ const content = action.messages.map(m => m.content).join('\n\n')
606
+ const trimmed = content.trim().toLowerCase()
607
+ const isPlaceholder = !trimmed || trimmed === 'reasoning' || trimmed === 'reasoning summary' || trimmed === 'thinking'
608
+
609
+ if (isPlaceholder) {
610
+ return (
611
+ <div className="flex items-center gap-2 py-1.5">
612
+ <span className="text-text-muted">•</span>
613
+ <ThinkingIndicator message="Thinking" />
614
+ </div>
615
+ )
616
+ }
617
+
618
+ return (
619
+ <div>
620
+ <button
621
+ onClick={() => setIsExpanded(!isExpanded)}
622
+ className="flex items-center gap-2 py-1.5 hover:bg-bg-hover/30 rounded px-2 -mx-2 transition-colors w-full text-left"
623
+ >
624
+ <span className="text-text-muted/60">•</span>
625
+ <Icons.ChevronDown
626
+ className={`w-3 h-3 text-text-muted transition-transform duration-200
627
+ ${isExpanded ? '' : '-rotate-90'}`}
628
+ />
629
+ <span className="text-xs text-text-muted font-medium">{action.label}</span>
630
+ {action.summary && (
631
+ <span className="text-[10px] text-text-muted/60 ml-1">{action.summary}</span>
632
+ )}
633
+ </button>
634
+ {isExpanded && (
635
+ <div className="pl-6 pb-2 pt-1">
636
+ <Markdown
637
+ content={content}
638
+ className="text-xs text-text-secondary leading-relaxed"
639
+ />
640
+ </div>
641
+ )}
642
+ </div>
643
+ )
644
+ }
645
+
646
+ const content = action.messages.map(m => m.content).join('\n---\n')
647
+ const hasContent = content.trim().length > 0
648
+
649
+ const getIcon = () => {
650
+ switch (action.type) {
651
+ case 'explored': return <Icons.Search className="w-3 h-3 text-text-muted/60" />
652
+ case 'edited': return <Icons.File className="w-3 h-3 text-accent-green/60" />
653
+ case 'ran': return <Icons.Terminal className="w-3 h-3 text-text-muted/60" />
654
+ case 'searched': return <Icons.Globe className="w-3 h-3 text-accent-green/60" />
655
+ default: return null
656
+ }
657
+ }
658
+
659
+ return (
660
+ <div>
661
+ <button
662
+ onClick={() => hasContent && setIsExpanded(!isExpanded)}
663
+ className={`flex items-center gap-2 py-1.5 rounded px-2 -mx-2 transition-colors w-full text-left
664
+ ${hasContent ? 'hover:bg-bg-hover/30 cursor-pointer' : 'cursor-default'}`}
665
+ >
666
+ {getIcon()}
667
+ <span className="text-xs text-text-primary font-medium">{action.label}</span>
668
+ {action.summary && (
669
+ <span className="text-xs text-text-muted ml-1 truncate max-w-[300px]">{action.summary}</span>
670
+ )}
671
+ </button>
672
+ {isExpanded && hasContent && (
673
+ <div className="pl-6 pb-2 pt-1">
674
+ <pre className="text-xs text-text-secondary whitespace-pre-wrap leading-relaxed font-mono bg-bg-primary/50 rounded p-2 max-h-[300px] overflow-y-auto">
675
+ {content}
676
+ </pre>
677
+ </div>
678
+ )}
679
+ </div>
680
+ )
681
+ }
682
+
683
+ function ApprovalCard({
684
+ approval,
685
+ onApprove,
686
+ onApproveForSession,
687
+ onDeny
688
+ }: {
689
+ approval: ApprovalRequest
690
+ onApprove: () => void
691
+ onApproveForSession?: () => void
692
+ onDeny: () => void
693
+ }) {
694
+ const [isExpanded, setIsExpanded] = useState(true)
695
+
696
+ const typeLabels = {
697
+ command: 'Run command',
698
+ file: 'Edit file',
699
+ network: 'Network request',
700
+ }
701
+
702
+ const getIcon = () => {
703
+ switch (approval.type) {
704
+ case 'command': return <Icons.Terminal className="w-3 h-3 text-yellow-500/70" />
705
+ case 'file': return <Icons.File className="w-3 h-3 text-yellow-500/70" />
706
+ case 'network': return <Icons.Globe className="w-3 h-3 text-yellow-500/70" />
707
+ default: return <Icons.Warning className="w-3 h-3 text-yellow-500/70" />
708
+ }
709
+ }
710
+
711
+ return (
712
+ <div className="pl-10">
713
+ <div>
714
+ <button
715
+ onClick={() => setIsExpanded(!isExpanded)}
716
+ className="flex items-center gap-2 py-1.5 hover:bg-bg-hover/30 rounded px-2 -mx-2 transition-colors w-full text-left"
717
+ >
718
+ {getIcon()}
719
+ <span className="text-xs text-yellow-500 font-medium">Approval needed</span>
720
+ <span className="text-xs text-text-muted ml-1">{typeLabels[approval.type]}</span>
721
+ </button>
722
+ {isExpanded && (
723
+ <div className="pl-6 pb-2 pt-1 space-y-2">
724
+ <pre className="text-xs text-text-secondary whitespace-pre-wrap leading-relaxed font-mono bg-bg-primary/50 rounded p-2 max-h-[200px] overflow-y-auto">
725
+ {approval.payload}
726
+ </pre>
727
+ <div className="flex gap-2">
728
+ <Button variant="primary" size="sm" onClick={onApprove}>
729
+ <Icons.Check className="w-3 h-3" />
730
+ Approve
731
+ </Button>
732
+ {onApproveForSession && (
733
+ <Button variant="ghost" size="sm" onClick={onApproveForSession}>
734
+ <Icons.Bolt className="w-3 h-3" />
735
+ Always
736
+ </Button>
737
+ )}
738
+ <Button variant="danger" size="sm" onClick={onDeny}>
739
+ <Icons.X className="w-3 h-3" />
740
+ Deny
741
+ </Button>
742
+ </div>
743
+ </div>
744
+ )}
745
+ </div>
746
+ </div>
747
+ )
748
+ }