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.
- package/README.md +26 -0
- package/apps/backend/README.md +46 -0
- package/apps/backend/bun.lock +64 -0
- package/apps/backend/package.json +18 -0
- package/apps/backend/scripts/generate-protocol.ts +32 -0
- package/apps/backend/src/analytics/service.ts +219 -0
- package/apps/backend/src/analytics/store.ts +284 -0
- package/apps/backend/src/config.ts +98 -0
- package/apps/backend/src/core/app-server.ts +131 -0
- package/apps/backend/src/core/jsonrpc.ts +166 -0
- package/apps/backend/src/protocol/AbsolutePathBuf.ts +14 -0
- package/apps/backend/src/protocol/AddConversationListenerParams.ts +6 -0
- package/apps/backend/src/protocol/AddConversationSubscriptionResponse.ts +5 -0
- package/apps/backend/src/protocol/AgentMessageContent.ts +5 -0
- package/apps/backend/src/protocol/AgentMessageContentDeltaEvent.ts +5 -0
- package/apps/backend/src/protocol/AgentMessageDeltaEvent.ts +5 -0
- package/apps/backend/src/protocol/AgentMessageEvent.ts +5 -0
- package/apps/backend/src/protocol/AgentMessageItem.ts +6 -0
- package/apps/backend/src/protocol/AgentReasoningDeltaEvent.ts +5 -0
- package/apps/backend/src/protocol/AgentReasoningEvent.ts +5 -0
- package/apps/backend/src/protocol/AgentReasoningRawContentDeltaEvent.ts +5 -0
- package/apps/backend/src/protocol/AgentReasoningRawContentEvent.ts +5 -0
- package/apps/backend/src/protocol/AgentReasoningSectionBreakEvent.ts +5 -0
- package/apps/backend/src/protocol/Annotations.ts +9 -0
- package/apps/backend/src/protocol/ApplyPatchApprovalParams.ts +21 -0
- package/apps/backend/src/protocol/ApplyPatchApprovalRequestEvent.ts +23 -0
- package/apps/backend/src/protocol/ApplyPatchApprovalResponse.ts +6 -0
- package/apps/backend/src/protocol/ArchiveConversationParams.ts +6 -0
- package/apps/backend/src/protocol/ArchiveConversationResponse.ts +5 -0
- package/apps/backend/src/protocol/AskForApproval.ts +9 -0
- package/apps/backend/src/protocol/AudioContent.ts +9 -0
- package/apps/backend/src/protocol/AuthMode.ts +5 -0
- package/apps/backend/src/protocol/AuthStatusChangeNotification.ts +9 -0
- package/apps/backend/src/protocol/BackgroundEventEvent.ts +5 -0
- package/apps/backend/src/protocol/BlobResourceContents.ts +5 -0
- package/apps/backend/src/protocol/CallToolResult.ts +10 -0
- package/apps/backend/src/protocol/CancelLoginChatGptParams.ts +5 -0
- package/apps/backend/src/protocol/CancelLoginChatGptResponse.ts +5 -0
- package/apps/backend/src/protocol/ClientInfo.ts +5 -0
- package/apps/backend/src/protocol/ClientNotification.ts +5 -0
- package/apps/backend/src/protocol/ClientRequest.ts +46 -0
- package/apps/backend/src/protocol/CodexErrorInfo.ts +8 -0
- package/apps/backend/src/protocol/ContentBlock.ts +10 -0
- package/apps/backend/src/protocol/ContentItem.ts +5 -0
- package/apps/backend/src/protocol/ContextCompactedEvent.ts +5 -0
- package/apps/backend/src/protocol/ConversationGitInfo.ts +5 -0
- package/apps/backend/src/protocol/ConversationId.ts +5 -0
- package/apps/backend/src/protocol/ConversationSummary.ts +8 -0
- package/apps/backend/src/protocol/CreditsSnapshot.ts +5 -0
- package/apps/backend/src/protocol/CustomPrompt.ts +5 -0
- package/apps/backend/src/protocol/DeprecationNoticeEvent.ts +13 -0
- package/apps/backend/src/protocol/ElicitationRequestEvent.ts +6 -0
- package/apps/backend/src/protocol/EmbeddedResource.ts +13 -0
- package/apps/backend/src/protocol/EmbeddedResourceResource.ts +7 -0
- package/apps/backend/src/protocol/ErrorEvent.ts +6 -0
- package/apps/backend/src/protocol/EventMsg.ts +60 -0
- package/apps/backend/src/protocol/ExecApprovalRequestEvent.ts +32 -0
- package/apps/backend/src/protocol/ExecCommandApprovalParams.ts +12 -0
- package/apps/backend/src/protocol/ExecCommandApprovalResponse.ts +6 -0
- package/apps/backend/src/protocol/ExecCommandBeginEvent.ts +35 -0
- package/apps/backend/src/protocol/ExecCommandEndEvent.ts +59 -0
- package/apps/backend/src/protocol/ExecCommandOutputDeltaEvent.ts +18 -0
- package/apps/backend/src/protocol/ExecCommandSource.ts +5 -0
- package/apps/backend/src/protocol/ExecOneOffCommandParams.ts +6 -0
- package/apps/backend/src/protocol/ExecOneOffCommandResponse.ts +5 -0
- package/apps/backend/src/protocol/ExecOutputStream.ts +5 -0
- package/apps/backend/src/protocol/ExecPolicyAmendment.ts +12 -0
- package/apps/backend/src/protocol/ExitedReviewModeEvent.ts +6 -0
- package/apps/backend/src/protocol/FileChange.ts +5 -0
- package/apps/backend/src/protocol/ForcedLoginMethod.ts +5 -0
- package/apps/backend/src/protocol/FunctionCallOutputContentItem.ts +9 -0
- package/apps/backend/src/protocol/FunctionCallOutputPayload.ts +15 -0
- package/apps/backend/src/protocol/FuzzyFileSearchParams.ts +5 -0
- package/apps/backend/src/protocol/FuzzyFileSearchResponse.ts +6 -0
- package/apps/backend/src/protocol/FuzzyFileSearchResult.ts +8 -0
- package/apps/backend/src/protocol/GetAuthStatusParams.ts +5 -0
- package/apps/backend/src/protocol/GetAuthStatusResponse.ts +6 -0
- package/apps/backend/src/protocol/GetConversationSummaryParams.ts +6 -0
- package/apps/backend/src/protocol/GetConversationSummaryResponse.ts +6 -0
- package/apps/backend/src/protocol/GetHistoryEntryResponseEvent.ts +10 -0
- package/apps/backend/src/protocol/GetUserAgentResponse.ts +5 -0
- package/apps/backend/src/protocol/GetUserSavedConfigResponse.ts +6 -0
- package/apps/backend/src/protocol/GhostCommit.ts +8 -0
- package/apps/backend/src/protocol/GitDiffToRemoteParams.ts +5 -0
- package/apps/backend/src/protocol/GitDiffToRemoteResponse.ts +6 -0
- package/apps/backend/src/protocol/GitSha.ts +5 -0
- package/apps/backend/src/protocol/HistoryEntry.ts +5 -0
- package/apps/backend/src/protocol/ImageContent.ts +9 -0
- package/apps/backend/src/protocol/InitializeParams.ts +6 -0
- package/apps/backend/src/protocol/InitializeResponse.ts +5 -0
- package/apps/backend/src/protocol/InputItem.ts +5 -0
- package/apps/backend/src/protocol/InterruptConversationParams.ts +6 -0
- package/apps/backend/src/protocol/InterruptConversationResponse.ts +6 -0
- package/apps/backend/src/protocol/ItemCompletedEvent.ts +7 -0
- package/apps/backend/src/protocol/ItemStartedEvent.ts +7 -0
- package/apps/backend/src/protocol/ListConversationsParams.ts +5 -0
- package/apps/backend/src/protocol/ListConversationsResponse.ts +6 -0
- package/apps/backend/src/protocol/ListCustomPromptsResponseEvent.ts +9 -0
- package/apps/backend/src/protocol/ListSkillsResponseEvent.ts +9 -0
- package/apps/backend/src/protocol/LocalShellAction.ts +6 -0
- package/apps/backend/src/protocol/LocalShellExecAction.ts +5 -0
- package/apps/backend/src/protocol/LocalShellStatus.ts +5 -0
- package/apps/backend/src/protocol/LoginApiKeyParams.ts +5 -0
- package/apps/backend/src/protocol/LoginApiKeyResponse.ts +5 -0
- package/apps/backend/src/protocol/LoginChatGptCompleteNotification.ts +8 -0
- package/apps/backend/src/protocol/LoginChatGptResponse.ts +5 -0
- package/apps/backend/src/protocol/LogoutChatGptResponse.ts +5 -0
- package/apps/backend/src/protocol/McpAuthStatus.ts +5 -0
- package/apps/backend/src/protocol/McpInvocation.ts +18 -0
- package/apps/backend/src/protocol/McpListToolsResponseEvent.ts +25 -0
- package/apps/backend/src/protocol/McpStartupCompleteEvent.ts +6 -0
- package/apps/backend/src/protocol/McpStartupFailure.ts +5 -0
- package/apps/backend/src/protocol/McpStartupStatus.ts +5 -0
- package/apps/backend/src/protocol/McpStartupUpdateEvent.ts +14 -0
- package/apps/backend/src/protocol/McpToolCallBeginEvent.ts +10 -0
- package/apps/backend/src/protocol/McpToolCallEndEvent.ts +15 -0
- package/apps/backend/src/protocol/NetworkAccess.ts +8 -0
- package/apps/backend/src/protocol/NewConversationParams.ts +8 -0
- package/apps/backend/src/protocol/NewConversationResponse.ts +7 -0
- package/apps/backend/src/protocol/ParsedCommand.ts +12 -0
- package/apps/backend/src/protocol/PatchApplyBeginEvent.ts +23 -0
- package/apps/backend/src/protocol/PatchApplyEndEvent.ts +31 -0
- package/apps/backend/src/protocol/PlanItemArg.ts +6 -0
- package/apps/backend/src/protocol/PlanType.ts +5 -0
- package/apps/backend/src/protocol/Profile.ts +9 -0
- package/apps/backend/src/protocol/README.md +11 -0
- package/apps/backend/src/protocol/RateLimitSnapshot.ts +8 -0
- package/apps/backend/src/protocol/RateLimitWindow.ts +17 -0
- package/apps/backend/src/protocol/RawResponseItemEvent.ts +6 -0
- package/apps/backend/src/protocol/ReasoningContentDeltaEvent.ts +5 -0
- package/apps/backend/src/protocol/ReasoningEffort.ts +8 -0
- package/apps/backend/src/protocol/ReasoningItem.ts +5 -0
- package/apps/backend/src/protocol/ReasoningItemContent.ts +5 -0
- package/apps/backend/src/protocol/ReasoningItemReasoningSummary.ts +5 -0
- package/apps/backend/src/protocol/ReasoningRawContentDeltaEvent.ts +5 -0
- package/apps/backend/src/protocol/ReasoningSummary.ts +10 -0
- package/apps/backend/src/protocol/RemoveConversationListenerParams.ts +5 -0
- package/apps/backend/src/protocol/RemoveConversationSubscriptionResponse.ts +5 -0
- package/apps/backend/src/protocol/RequestId.ts +5 -0
- package/apps/backend/src/protocol/Resource.ts +9 -0
- package/apps/backend/src/protocol/ResourceLink.ts +11 -0
- package/apps/backend/src/protocol/ResourceTemplate.ts +9 -0
- package/apps/backend/src/protocol/ResponseItem.ts +17 -0
- package/apps/backend/src/protocol/ResumeConversationParams.ts +8 -0
- package/apps/backend/src/protocol/ResumeConversationResponse.ts +7 -0
- package/apps/backend/src/protocol/ReviewCodeLocation.ts +9 -0
- package/apps/backend/src/protocol/ReviewDecision.ts +9 -0
- package/apps/backend/src/protocol/ReviewFinding.ts +9 -0
- package/apps/backend/src/protocol/ReviewLineRange.ts +8 -0
- package/apps/backend/src/protocol/ReviewOutputEvent.ts +9 -0
- package/apps/backend/src/protocol/ReviewRequest.ts +9 -0
- package/apps/backend/src/protocol/ReviewTarget.ts +9 -0
- package/apps/backend/src/protocol/Role.ts +8 -0
- package/apps/backend/src/protocol/SandboxMode.ts +5 -0
- package/apps/backend/src/protocol/SandboxPolicy.ts +35 -0
- package/apps/backend/src/protocol/SandboxSettings.ts +6 -0
- package/apps/backend/src/protocol/SendUserMessageParams.ts +7 -0
- package/apps/backend/src/protocol/SendUserMessageResponse.ts +5 -0
- package/apps/backend/src/protocol/SendUserTurnParams.ts +11 -0
- package/apps/backend/src/protocol/SendUserTurnResponse.ts +5 -0
- package/apps/backend/src/protocol/ServerNotification.ts +36 -0
- package/apps/backend/src/protocol/ServerRequest.ts +13 -0
- package/apps/backend/src/protocol/SessionConfiguredEvent.ts +48 -0
- package/apps/backend/src/protocol/SessionConfiguredNotification.ts +8 -0
- package/apps/backend/src/protocol/SessionSource.ts +6 -0
- package/apps/backend/src/protocol/SetDefaultModelParams.ts +6 -0
- package/apps/backend/src/protocol/SetDefaultModelResponse.ts +5 -0
- package/apps/backend/src/protocol/SkillErrorInfo.ts +5 -0
- package/apps/backend/src/protocol/SkillMetadata.ts +6 -0
- package/apps/backend/src/protocol/SkillScope.ts +5 -0
- package/apps/backend/src/protocol/SkillsListEntry.ts +7 -0
- package/apps/backend/src/protocol/StepStatus.ts +5 -0
- package/apps/backend/src/protocol/StreamErrorEvent.ts +6 -0
- package/apps/backend/src/protocol/SubAgentSource.ts +5 -0
- package/apps/backend/src/protocol/TaskCompleteEvent.ts +5 -0
- package/apps/backend/src/protocol/TaskStartedEvent.ts +5 -0
- package/apps/backend/src/protocol/TerminalInteractionEvent.ts +17 -0
- package/apps/backend/src/protocol/TextContent.ts +9 -0
- package/apps/backend/src/protocol/TextResourceContents.ts +5 -0
- package/apps/backend/src/protocol/TokenCountEvent.ts +7 -0
- package/apps/backend/src/protocol/TokenUsage.ts +5 -0
- package/apps/backend/src/protocol/TokenUsageInfo.ts +6 -0
- package/apps/backend/src/protocol/Tool.ts +11 -0
- package/apps/backend/src/protocol/ToolAnnotations.ts +15 -0
- package/apps/backend/src/protocol/ToolInputSchema.ts +9 -0
- package/apps/backend/src/protocol/ToolOutputSchema.ts +10 -0
- package/apps/backend/src/protocol/Tools.ts +5 -0
- package/apps/backend/src/protocol/TurnAbortReason.ts +5 -0
- package/apps/backend/src/protocol/TurnAbortedEvent.ts +6 -0
- package/apps/backend/src/protocol/TurnDiffEvent.ts +5 -0
- package/apps/backend/src/protocol/TurnItem.ts +9 -0
- package/apps/backend/src/protocol/UndoCompletedEvent.ts +5 -0
- package/apps/backend/src/protocol/UndoStartedEvent.ts +5 -0
- package/apps/backend/src/protocol/UpdatePlanArgs.ts +6 -0
- package/apps/backend/src/protocol/UserInfoResponse.ts +5 -0
- package/apps/backend/src/protocol/UserInput.ts +8 -0
- package/apps/backend/src/protocol/UserMessageEvent.ts +5 -0
- package/apps/backend/src/protocol/UserMessageItem.ts +6 -0
- package/apps/backend/src/protocol/UserSavedConfig.ts +14 -0
- package/apps/backend/src/protocol/Verbosity.ts +9 -0
- package/apps/backend/src/protocol/ViewImageToolCallEvent.ts +13 -0
- package/apps/backend/src/protocol/WarningEvent.ts +5 -0
- package/apps/backend/src/protocol/WebSearchAction.ts +5 -0
- package/apps/backend/src/protocol/WebSearchBeginEvent.ts +5 -0
- package/apps/backend/src/protocol/WebSearchEndEvent.ts +5 -0
- package/apps/backend/src/protocol/WebSearchItem.ts +5 -0
- package/apps/backend/src/protocol/index.ts +198 -0
- package/apps/backend/src/protocol/serde_json/JsonValue.ts +5 -0
- package/apps/backend/src/protocol/v2/Account.ts +6 -0
- package/apps/backend/src/protocol/v2/AccountLoginCompletedNotification.ts +5 -0
- package/apps/backend/src/protocol/v2/AccountRateLimitsUpdatedNotification.ts +6 -0
- package/apps/backend/src/protocol/v2/AccountUpdatedNotification.ts +6 -0
- package/apps/backend/src/protocol/v2/AgentMessageDeltaNotification.ts +5 -0
- package/apps/backend/src/protocol/v2/ApprovalDecision.ts +6 -0
- package/apps/backend/src/protocol/v2/AskForApproval.ts +5 -0
- package/apps/backend/src/protocol/v2/CancelLoginAccountParams.ts +5 -0
- package/apps/backend/src/protocol/v2/CancelLoginAccountResponse.ts +6 -0
- package/apps/backend/src/protocol/v2/CancelLoginAccountStatus.ts +5 -0
- package/apps/backend/src/protocol/v2/CodexErrorInfo.ts +11 -0
- package/apps/backend/src/protocol/v2/CommandAction.ts +5 -0
- package/apps/backend/src/protocol/v2/CommandExecParams.ts +6 -0
- package/apps/backend/src/protocol/v2/CommandExecResponse.ts +5 -0
- package/apps/backend/src/protocol/v2/CommandExecutionOutputDeltaNotification.ts +5 -0
- package/apps/backend/src/protocol/v2/CommandExecutionRequestApprovalParams.ts +14 -0
- package/apps/backend/src/protocol/v2/CommandExecutionRequestApprovalResponse.ts +6 -0
- package/apps/backend/src/protocol/v2/CommandExecutionStatus.ts +5 -0
- package/apps/backend/src/protocol/v2/Config.ts +15 -0
- package/apps/backend/src/protocol/v2/ConfigBatchWriteParams.ts +10 -0
- package/apps/backend/src/protocol/v2/ConfigEdit.ts +7 -0
- package/apps/backend/src/protocol/v2/ConfigLayer.ts +7 -0
- package/apps/backend/src/protocol/v2/ConfigLayerMetadata.ts +6 -0
- package/apps/backend/src/protocol/v2/ConfigLayerSource.ts +6 -0
- package/apps/backend/src/protocol/v2/ConfigReadParams.ts +5 -0
- package/apps/backend/src/protocol/v2/ConfigReadResponse.ts +8 -0
- package/apps/backend/src/protocol/v2/ConfigValueWriteParams.ts +11 -0
- package/apps/backend/src/protocol/v2/ConfigWriteResponse.ts +12 -0
- package/apps/backend/src/protocol/v2/ContextCompactedNotification.ts +5 -0
- package/apps/backend/src/protocol/v2/CreditsSnapshot.ts +5 -0
- package/apps/backend/src/protocol/v2/DeprecationNoticeNotification.ts +13 -0
- package/apps/backend/src/protocol/v2/ErrorNotification.ts +6 -0
- package/apps/backend/src/protocol/v2/ExecPolicyAmendment.ts +5 -0
- package/apps/backend/src/protocol/v2/FeedbackUploadParams.ts +5 -0
- package/apps/backend/src/protocol/v2/FeedbackUploadResponse.ts +5 -0
- package/apps/backend/src/protocol/v2/FileChangeOutputDeltaNotification.ts +5 -0
- package/apps/backend/src/protocol/v2/FileChangeRequestApprovalParams.ts +14 -0
- package/apps/backend/src/protocol/v2/FileChangeRequestApprovalResponse.ts +6 -0
- package/apps/backend/src/protocol/v2/FileUpdateChange.ts +6 -0
- package/apps/backend/src/protocol/v2/GetAccountParams.ts +5 -0
- package/apps/backend/src/protocol/v2/GetAccountRateLimitsResponse.ts +6 -0
- package/apps/backend/src/protocol/v2/GetAccountResponse.ts +6 -0
- package/apps/backend/src/protocol/v2/GitInfo.ts +5 -0
- package/apps/backend/src/protocol/v2/ItemCompletedNotification.ts +6 -0
- package/apps/backend/src/protocol/v2/ItemStartedNotification.ts +6 -0
- package/apps/backend/src/protocol/v2/ListMcpServerStatusParams.ts +13 -0
- package/apps/backend/src/protocol/v2/ListMcpServerStatusResponse.ts +11 -0
- package/apps/backend/src/protocol/v2/LoginAccountParams.ts +5 -0
- package/apps/backend/src/protocol/v2/LoginAccountResponse.ts +9 -0
- package/apps/backend/src/protocol/v2/LogoutAccountResponse.ts +5 -0
- package/apps/backend/src/protocol/v2/McpAuthStatus.ts +5 -0
- package/apps/backend/src/protocol/v2/McpServerOauthLoginCompletedNotification.ts +5 -0
- package/apps/backend/src/protocol/v2/McpServerOauthLoginParams.ts +5 -0
- package/apps/backend/src/protocol/v2/McpServerOauthLoginResponse.ts +5 -0
- package/apps/backend/src/protocol/v2/McpServerStatus.ts +9 -0
- package/apps/backend/src/protocol/v2/McpToolCallError.ts +5 -0
- package/apps/backend/src/protocol/v2/McpToolCallProgressNotification.ts +5 -0
- package/apps/backend/src/protocol/v2/McpToolCallResult.ts +7 -0
- package/apps/backend/src/protocol/v2/McpToolCallStatus.ts +5 -0
- package/apps/backend/src/protocol/v2/MergeStrategy.ts +5 -0
- package/apps/backend/src/protocol/v2/Model.ts +7 -0
- package/apps/backend/src/protocol/v2/ModelListParams.ts +13 -0
- package/apps/backend/src/protocol/v2/ModelListResponse.ts +11 -0
- package/apps/backend/src/protocol/v2/NetworkAccess.ts +5 -0
- package/apps/backend/src/protocol/v2/OverriddenMetadata.ts +7 -0
- package/apps/backend/src/protocol/v2/PatchApplyStatus.ts +5 -0
- package/apps/backend/src/protocol/v2/PatchChangeKind.ts +5 -0
- package/apps/backend/src/protocol/v2/ProfileV2.ts +10 -0
- package/apps/backend/src/protocol/v2/RateLimitSnapshot.ts +8 -0
- package/apps/backend/src/protocol/v2/RateLimitWindow.ts +5 -0
- package/apps/backend/src/protocol/v2/RawResponseItemCompletedNotification.ts +6 -0
- package/apps/backend/src/protocol/v2/ReasoningEffortOption.ts +6 -0
- package/apps/backend/src/protocol/v2/ReasoningSummaryPartAddedNotification.ts +5 -0
- package/apps/backend/src/protocol/v2/ReasoningSummaryTextDeltaNotification.ts +5 -0
- package/apps/backend/src/protocol/v2/ReasoningTextDeltaNotification.ts +5 -0
- package/apps/backend/src/protocol/v2/ReviewDelivery.ts +5 -0
- package/apps/backend/src/protocol/v2/ReviewStartParams.ts +12 -0
- package/apps/backend/src/protocol/v2/ReviewStartResponse.ts +13 -0
- package/apps/backend/src/protocol/v2/ReviewTarget.ts +9 -0
- package/apps/backend/src/protocol/v2/SandboxMode.ts +5 -0
- package/apps/backend/src/protocol/v2/SandboxPolicy.ts +7 -0
- package/apps/backend/src/protocol/v2/SandboxWorkspaceWrite.ts +5 -0
- package/apps/backend/src/protocol/v2/SessionSource.ts +5 -0
- package/apps/backend/src/protocol/v2/SkillErrorInfo.ts +5 -0
- package/apps/backend/src/protocol/v2/SkillMetadata.ts +6 -0
- package/apps/backend/src/protocol/v2/SkillScope.ts +5 -0
- package/apps/backend/src/protocol/v2/SkillsListEntry.ts +7 -0
- package/apps/backend/src/protocol/v2/SkillsListParams.ts +13 -0
- package/apps/backend/src/protocol/v2/SkillsListResponse.ts +6 -0
- package/apps/backend/src/protocol/v2/TerminalInteractionNotification.ts +5 -0
- package/apps/backend/src/protocol/v2/Thread.ts +46 -0
- package/apps/backend/src/protocol/v2/ThreadArchiveParams.ts +5 -0
- package/apps/backend/src/protocol/v2/ThreadArchiveResponse.ts +5 -0
- package/apps/backend/src/protocol/v2/ThreadItem.ts +48 -0
- package/apps/backend/src/protocol/v2/ThreadListParams.ts +18 -0
- package/apps/backend/src/protocol/v2/ThreadListResponse.ts +11 -0
- package/apps/backend/src/protocol/v2/ThreadResumeParams.ts +35 -0
- package/apps/backend/src/protocol/v2/ThreadResumeResponse.ts +9 -0
- package/apps/backend/src/protocol/v2/ThreadStartParams.ts +15 -0
- package/apps/backend/src/protocol/v2/ThreadStartResponse.ts +9 -0
- package/apps/backend/src/protocol/v2/ThreadStartedNotification.ts +6 -0
- package/apps/backend/src/protocol/v2/ThreadTokenUsage.ts +6 -0
- package/apps/backend/src/protocol/v2/ThreadTokenUsageUpdatedNotification.ts +6 -0
- package/apps/backend/src/protocol/v2/TokenUsageBreakdown.ts +5 -0
- package/apps/backend/src/protocol/v2/ToolsV2.ts +5 -0
- package/apps/backend/src/protocol/v2/Turn.ts +18 -0
- package/apps/backend/src/protocol/v2/TurnCompletedNotification.ts +6 -0
- package/apps/backend/src/protocol/v2/TurnDiffUpdatedNotification.ts +9 -0
- package/apps/backend/src/protocol/v2/TurnError.ts +6 -0
- package/apps/backend/src/protocol/v2/TurnInterruptParams.ts +5 -0
- package/apps/backend/src/protocol/v2/TurnInterruptResponse.ts +5 -0
- package/apps/backend/src/protocol/v2/TurnPlanStep.ts +6 -0
- package/apps/backend/src/protocol/v2/TurnPlanStepStatus.ts +5 -0
- package/apps/backend/src/protocol/v2/TurnPlanUpdatedNotification.ts +6 -0
- package/apps/backend/src/protocol/v2/TurnStartParams.ts +34 -0
- package/apps/backend/src/protocol/v2/TurnStartResponse.ts +6 -0
- package/apps/backend/src/protocol/v2/TurnStartedNotification.ts +6 -0
- package/apps/backend/src/protocol/v2/TurnStatus.ts +5 -0
- package/apps/backend/src/protocol/v2/UserInput.ts +5 -0
- package/apps/backend/src/protocol/v2/WindowsWorldWritableWarningNotification.ts +5 -0
- package/apps/backend/src/protocol/v2/WriteStatus.ts +5 -0
- package/apps/backend/src/protocol/v2/index.ts +123 -0
- package/apps/backend/src/reviews/service.ts +27 -0
- package/apps/backend/src/reviews/store.ts +124 -0
- package/apps/backend/src/server.ts +531 -0
- package/apps/backend/src/services/profile-store.ts +114 -0
- package/apps/backend/src/services/supervisor.ts +102 -0
- package/apps/backend/src/thread-index/service.ts +75 -0
- package/apps/backend/src/thread-index/store.ts +195 -0
- package/apps/backend/src/ws/messages.ts +73 -0
- package/apps/backend/tsconfig.json +20 -0
- package/apps/web/README.md +24 -0
- package/apps/web/bun.lock +1062 -0
- package/apps/web/eslint.config.js +23 -0
- package/apps/web/index.html +16 -0
- package/apps/web/package.json +38 -0
- package/apps/web/src/app.tsx +83 -0
- package/apps/web/src/components/composer/slash-command-menu.tsx +47 -0
- package/apps/web/src/components/index.ts +2 -0
- package/apps/web/src/components/layout/account-usage-panel.tsx +167 -0
- package/apps/web/src/components/layout/analytics-view.tsx +296 -0
- package/apps/web/src/components/layout/index.ts +7 -0
- package/apps/web/src/components/layout/mobile-header.tsx +56 -0
- package/apps/web/src/components/layout/reviews-view.tsx +848 -0
- package/apps/web/src/components/layout/session-view.tsx +1374 -0
- package/apps/web/src/components/layout/settings-dialog.tsx +322 -0
- package/apps/web/src/components/layout/side-bar.tsx +417 -0
- package/apps/web/src/components/layout/thread-list.tsx +488 -0
- package/apps/web/src/components/layout/virtualized-message-list.tsx +748 -0
- package/apps/web/src/components/loading/startup-ascii.ts +652 -0
- package/apps/web/src/components/loading/startup-loader.tsx +37 -0
- package/apps/web/src/components/session-view/file-mention-menu.tsx +46 -0
- package/apps/web/src/components/session-view/session-auth-banner.tsx +61 -0
- package/apps/web/src/components/session-view/session-composer.tsx +328 -0
- package/apps/web/src/components/session-view/session-dialogs.tsx +280 -0
- package/apps/web/src/components/session-view/session-empty.tsx +47 -0
- package/apps/web/src/components/session-view/session-header.tsx +49 -0
- package/apps/web/src/components/ui/avatar.tsx +19 -0
- package/apps/web/src/components/ui/badge.tsx +21 -0
- package/apps/web/src/components/ui/button.tsx +47 -0
- package/apps/web/src/components/ui/collapsible-content.tsx +114 -0
- package/apps/web/src/components/ui/contribution-graph.tsx +182 -0
- package/apps/web/src/components/ui/dialog-box.tsx +203 -0
- package/apps/web/src/components/ui/icon-button.tsx +32 -0
- package/apps/web/src/components/ui/icons.tsx +187 -0
- package/apps/web/src/components/ui/index.tsx +15 -0
- package/apps/web/src/components/ui/input.tsx +43 -0
- package/apps/web/src/components/ui/markdown-stream.tsx +21 -0
- package/apps/web/src/components/ui/mobile-drawer.tsx +124 -0
- package/apps/web/src/components/ui/section-header.tsx +13 -0
- package/apps/web/src/components/ui/select.tsx +217 -0
- package/apps/web/src/components/ui/shimmer.tsx +138 -0
- package/apps/web/src/components/ui/status-dot.tsx +24 -0
- package/apps/web/src/config.ts +5 -0
- package/apps/web/src/hooks/index.ts +3 -0
- package/apps/web/src/hooks/use-analytics.ts +122 -0
- package/apps/web/src/hooks/use-hub-connection.ts +587 -0
- package/apps/web/src/hooks/use-mobile.ts +76 -0
- package/apps/web/src/hooks/use-thread-history.ts +210 -0
- package/apps/web/src/index.css +269 -0
- package/apps/web/src/main.tsx +10 -0
- package/apps/web/src/services/hub-client.ts +358 -0
- package/apps/web/src/store/index.ts +528 -0
- package/apps/web/src/types/index.ts +119 -0
- package/apps/web/src/utils/account-refresh.ts +168 -0
- package/apps/web/src/utils/approval-policy.ts +53 -0
- package/apps/web/src/utils/init-prompt.ts +41 -0
- package/apps/web/src/utils/item-format.ts +170 -0
- package/apps/web/src/utils/prompt-expander.ts +62 -0
- package/apps/web/src/utils/reasoning-summary.ts +48 -0
- package/apps/web/src/utils/slash-commands.ts +98 -0
- package/apps/web/tsconfig.app.json +28 -0
- package/apps/web/tsconfig.json +7 -0
- package/apps/web/tsconfig.node.json +26 -0
- package/apps/web/vite.config.ts +8 -0
- package/bin/better-codex.cjs +199 -0
- 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
|
+
}
|