@adecore/agents-react 0.0.1 → 0.17.0-beta.1
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/LICENSE +110 -0
- package/README.md +53 -0
- package/dist/agents/AccountDot.d.ts +4 -0
- package/dist/agents/AccountDot.js +8 -0
- package/dist/agents/AgentIcon.d.ts +6 -0
- package/dist/agents/AgentIcon.js +20 -0
- package/dist/agents/ModelOptionControl.d.ts +15 -0
- package/dist/agents/ModelOptionControl.js +33 -0
- package/dist/agents/ProviderLogo.d.ts +8 -0
- package/dist/agents/ProviderLogo.js +8 -0
- package/dist/agents/account-limits.d.ts +7 -0
- package/dist/agents/account-limits.js +40 -0
- package/dist/agents/accounts.d.ts +22 -0
- package/dist/agents/accounts.js +113 -0
- package/dist/agents/model-name.d.ts +12 -0
- package/dist/agents/model-name.js +56 -0
- package/dist/agents/model-options.d.ts +5 -0
- package/dist/agents/model-options.js +15 -0
- package/dist/agents/provider-paths.d.ts +2 -0
- package/dist/agents/provider-paths.js +8 -0
- package/dist/agents/status-look.d.ts +13 -0
- package/dist/agents/status-look.js +24 -0
- package/dist/chat/account-choice.d.ts +11 -0
- package/dist/chat/account-choice.js +30 -0
- package/dist/chat/actions.d.ts +6 -0
- package/dist/chat/actions.js +68 -0
- package/dist/chat/activity.d.ts +5 -0
- package/dist/chat/activity.js +6 -0
- package/dist/chat/attachments.d.ts +23 -0
- package/dist/chat/attachments.js +86 -0
- package/dist/chat/bookmarks.d.ts +14 -0
- package/dist/chat/bookmarks.js +73 -0
- package/dist/chat/chat-client.d.ts +55 -0
- package/dist/chat/chat-client.js +279 -0
- package/dist/chat/chat-references.d.ts +5 -0
- package/dist/chat/chat-references.js +5 -0
- package/dist/chat/drafts.d.ts +29 -0
- package/dist/chat/drafts.js +116 -0
- package/dist/chat/forks.d.ts +2 -0
- package/dist/chat/forks.js +28 -0
- package/dist/chat/guards.d.ts +12 -0
- package/dist/chat/guards.js +69 -0
- package/dist/chat/logic/ansi.d.ts +13 -0
- package/dist/chat/logic/ansi.js +185 -0
- package/dist/chat/logic/answers.d.ts +1 -0
- package/dist/chat/logic/answers.js +8 -0
- package/dist/chat/logic/bookmarks.d.ts +5 -0
- package/dist/chat/logic/bookmarks.js +33 -0
- package/dist/chat/logic/context-usage.d.ts +12 -0
- package/dist/chat/logic/context-usage.js +21 -0
- package/dist/chat/logic/fork.d.ts +45 -0
- package/dist/chat/logic/fork.js +148 -0
- package/dist/chat/logic/handback.d.ts +2 -0
- package/dist/chat/logic/handback.js +8 -0
- package/dist/chat/logic/json.d.ts +1 -0
- package/dist/chat/logic/json.js +4 -0
- package/dist/chat/logic/limit.d.ts +7 -0
- package/dist/chat/logic/limit.js +34 -0
- package/dist/chat/logic/resume-compaction.d.ts +14 -0
- package/dist/chat/logic/resume-compaction.js +47 -0
- package/dist/chat/logic/scrubber.d.ts +50 -0
- package/dist/chat/logic/scrubber.js +220 -0
- package/dist/chat/logic/thread-cards.d.ts +3 -0
- package/dist/chat/logic/thread-cards.js +59 -0
- package/dist/chat/logic/timeline-copy.d.ts +4 -0
- package/dist/chat/logic/timeline-copy.js +59 -0
- package/dist/chat/logic/timeline-target.d.ts +12 -0
- package/dist/chat/logic/timeline-target.js +34 -0
- package/dist/chat/logic/timeline.d.ts +115 -0
- package/dist/chat/logic/timeline.js +343 -0
- package/dist/chat/logic/tool-catalog.d.ts +10 -0
- package/dist/chat/logic/tool-catalog.js +44 -0
- package/dist/chat/logic/tools.d.ts +15 -0
- package/dist/chat/logic/tools.js +82 -0
- package/dist/chat/logic/welcome.d.ts +2 -0
- package/dist/chat/logic/welcome.js +10 -0
- package/dist/chat/logic/workflow.d.ts +14 -0
- package/dist/chat/logic/workflow.js +47 -0
- package/dist/chat/mentions.d.ts +65 -0
- package/dist/chat/mentions.js +184 -0
- package/dist/chat/persisted-json.d.ts +5 -0
- package/dist/chat/persisted-json.js +58 -0
- package/dist/chat/preferences.d.ts +27 -0
- package/dist/chat/preferences.js +139 -0
- package/dist/chat/quote.d.ts +2 -0
- package/dist/chat/quote.js +25 -0
- package/dist/chat/recent-messages.d.ts +17 -0
- package/dist/chat/recent-messages.js +36 -0
- package/dist/chat/resume-compaction-dismissals.d.ts +3 -0
- package/dist/chat/resume-compaction-dismissals.js +14 -0
- package/dist/chat/runtime-modes.d.ts +4 -0
- package/dist/chat/runtime-modes.js +15 -0
- package/dist/chat/stash.d.ts +24 -0
- package/dist/chat/stash.js +91 -0
- package/dist/chat/subagent-conversation.d.ts +41 -0
- package/dist/chat/subagent-conversation.js +167 -0
- package/dist/chat/subagent-list.d.ts +19 -0
- package/dist/chat/subagent-list.js +106 -0
- package/dist/chat/subagent-support.d.ts +6 -0
- package/dist/chat/subagent-support.js +14 -0
- package/dist/chat/subagent-view.d.ts +35 -0
- package/dist/chat/subagent-view.js +96 -0
- package/dist/chat/thumbnails.d.ts +10 -0
- package/dist/chat/thumbnails.js +89 -0
- package/dist/chat/timeline-flash.d.ts +12 -0
- package/dist/chat/timeline-flash.js +16 -0
- package/dist/chat/timeline-scroll.d.ts +27 -0
- package/dist/chat/timeline-scroll.js +152 -0
- package/dist/chat/ui/AccountPill.d.ts +3 -0
- package/dist/chat/ui/AccountPill.js +23 -0
- package/dist/chat/ui/AnsiOutput.d.ts +5 -0
- package/dist/chat/ui/AnsiOutput.js +9 -0
- package/dist/chat/ui/BookmarkMarker.d.ts +8 -0
- package/dist/chat/ui/BookmarkMarker.js +57 -0
- package/dist/chat/ui/BookmarkSubmenu.d.ts +3 -0
- package/dist/chat/ui/BookmarkSubmenu.js +33 -0
- package/dist/chat/ui/ChatActivity.d.ts +7 -0
- package/dist/chat/ui/ChatActivity.js +84 -0
- package/dist/chat/ui/ChatReferenceChip.d.ts +4 -0
- package/dist/chat/ui/ChatReferenceChip.js +16 -0
- package/dist/chat/ui/CodeBlock.d.ts +4 -0
- package/dist/chat/ui/CodeBlock.js +132 -0
- package/dist/chat/ui/Composer.d.ts +15 -0
- package/dist/chat/ui/Composer.js +856 -0
- package/dist/chat/ui/ComposerInput.d.ts +29 -0
- package/dist/chat/ui/ComposerInput.js +127 -0
- package/dist/chat/ui/DiffPool.d.ts +5 -0
- package/dist/chat/ui/DiffPool.js +26 -0
- package/dist/chat/ui/EditDiff.d.ts +4 -0
- package/dist/chat/ui/EditDiff.js +21 -0
- package/dist/chat/ui/FadingWords.d.ts +3 -0
- package/dist/chat/ui/FadingWords.js +5 -0
- package/dist/chat/ui/ImageView.d.ts +6 -0
- package/dist/chat/ui/ImageView.js +81 -0
- package/dist/chat/ui/LimitState.d.ts +8 -0
- package/dist/chat/ui/LimitState.js +112 -0
- package/dist/chat/ui/Markdown.d.ts +18 -0
- package/dist/chat/ui/Markdown.js +159 -0
- package/dist/chat/ui/MessageActions.d.ts +9 -0
- package/dist/chat/ui/MessageActions.js +53 -0
- package/dist/chat/ui/Pickers.d.ts +21 -0
- package/dist/chat/ui/Pickers.js +111 -0
- package/dist/chat/ui/PromptComposer.d.ts +13 -0
- package/dist/chat/ui/PromptComposer.js +56 -0
- package/dist/chat/ui/QuoteButton.d.ts +7 -0
- package/dist/chat/ui/QuoteButton.js +74 -0
- package/dist/chat/ui/ResumeCompactionDock.d.ts +5 -0
- package/dist/chat/ui/ResumeCompactionDock.js +14 -0
- package/dist/chat/ui/RunSettings.d.ts +28 -0
- package/dist/chat/ui/RunSettings.js +153 -0
- package/dist/chat/ui/Scrubber.d.ts +17 -0
- package/dist/chat/ui/Scrubber.js +118 -0
- package/dist/chat/ui/SubagentControls.d.ts +11 -0
- package/dist/chat/ui/SubagentControls.js +36 -0
- package/dist/chat/ui/SubagentInfo.d.ts +4 -0
- package/dist/chat/ui/SubagentInfo.js +34 -0
- package/dist/chat/ui/SubagentStopButton.d.ts +7 -0
- package/dist/chat/ui/SubagentStopButton.js +34 -0
- package/dist/chat/ui/SubagentTimeline.d.ts +4 -0
- package/dist/chat/ui/SubagentTimeline.js +107 -0
- package/dist/chat/ui/Timeline.d.ts +6 -0
- package/dist/chat/ui/Timeline.js +424 -0
- package/dist/chat/ui/TimelineMenu.d.ts +12 -0
- package/dist/chat/ui/TimelineMenu.js +54 -0
- package/dist/chat/ui/UnifiedDiff.d.ts +11 -0
- package/dist/chat/ui/UnifiedDiff.js +66 -0
- package/dist/chat/ui/UploadThumb.d.ts +4 -0
- package/dist/chat/ui/UploadThumb.js +11 -0
- package/dist/chat/ui/Welcome.d.ts +4 -0
- package/dist/chat/ui/Welcome.js +16 -0
- package/dist/chat/ui/chips.d.ts +4 -0
- package/dist/chat/ui/chips.js +11 -0
- package/dist/chat/ui/code-lines.d.ts +18 -0
- package/dist/chat/ui/code-lines.js +46 -0
- package/dist/chat/ui/code-streaming.d.ts +1 -0
- package/dist/chat/ui/code-streaming.js +3 -0
- package/dist/chat/ui/code-theme.d.ts +3 -0
- package/dist/chat/ui/code-theme.js +12 -0
- package/dist/chat/ui/composer/chips.d.ts +6 -0
- package/dist/chat/ui/composer/chips.js +31 -0
- package/dist/chat/ui/composer/editor.d.ts +11 -0
- package/dist/chat/ui/composer/editor.js +98 -0
- package/dist/chat/ui/composer/insert.d.ts +2 -0
- package/dist/chat/ui/composer/insert.js +19 -0
- package/dist/chat/ui/composer/keys.d.ts +30 -0
- package/dist/chat/ui/composer/keys.js +100 -0
- package/dist/chat/ui/diff-theme.d.ts +2 -0
- package/dist/chat/ui/diff-theme.js +20 -0
- package/dist/chat/ui/file-links.d.ts +5 -0
- package/dist/chat/ui/file-links.js +20 -0
- package/dist/chat/ui/find-reveal.d.ts +7 -0
- package/dist/chat/ui/find-reveal.js +13 -0
- package/dist/chat/ui/full-diff.d.ts +6 -0
- package/dist/chat/ui/full-diff.js +19 -0
- package/dist/chat/ui/icons.d.ts +4 -0
- package/dist/chat/ui/icons.js +20 -0
- package/dist/chat/ui/markdown-blocks.d.ts +6 -0
- package/dist/chat/ui/markdown-blocks.js +103 -0
- package/dist/chat/ui/quote-selection.d.ts +3 -0
- package/dist/chat/ui/quote-selection.js +46 -0
- package/dist/chat/ui/rehype-chips.d.ts +23 -0
- package/dist/chat/ui/rehype-chips.js +32 -0
- package/dist/chat/ui/rehype-fade.d.ts +23 -0
- package/dist/chat/ui/rehype-fade.js +36 -0
- package/dist/chat/ui/remark-html-as-text.d.ts +8 -0
- package/dist/chat/ui/remark-html-as-text.js +26 -0
- package/dist/chat/ui/reply-context.d.ts +5 -0
- package/dist/chat/ui/reply-context.js +2 -0
- package/dist/chat/ui/reveal.d.ts +13 -0
- package/dist/chat/ui/reveal.js +114 -0
- package/dist/chat/ui/rows/ForksRow.d.ts +4 -0
- package/dist/chat/ui/rows/ForksRow.js +38 -0
- package/dist/chat/ui/rows/MessageRows.d.ts +40 -0
- package/dist/chat/ui/rows/MessageRows.js +204 -0
- package/dist/chat/ui/rows/Rows.d.ts +12 -0
- package/dist/chat/ui/rows/Rows.js +61 -0
- package/dist/chat/ui/rows/SubagentRow.d.ts +17 -0
- package/dist/chat/ui/rows/SubagentRow.js +101 -0
- package/dist/chat/ui/rows/TaskRow.d.ts +8 -0
- package/dist/chat/ui/rows/TaskRow.js +63 -0
- package/dist/chat/ui/rows/WorkRows.d.ts +47 -0
- package/dist/chat/ui/rows/WorkRows.js +168 -0
- package/dist/chat/ui/rows/WorkflowRow.d.ts +8 -0
- package/dist/chat/ui/rows/WorkflowRow.js +48 -0
- package/dist/chat/ui/rows/row-rhythm.d.ts +6 -0
- package/dist/chat/ui/rows/row-rhythm.js +34 -0
- package/dist/chat/ui/useToggleSet.d.ts +6 -0
- package/dist/chat/ui/useToggleSet.js +25 -0
- package/dist/host.d.ts +195 -0
- package/dist/host.js +55 -0
- package/dist/lazy.d.ts +6 -0
- package/dist/lazy.js +57 -0
- package/dist/locales/en/agent-chat.json +446 -0
- package/dist/locales/en/agent-prompts.json +42 -0
- package/dist/locales/en/agent-providers.json +131 -0
- package/dist/locales/en/agent-usage.json +131 -0
- package/dist/locales/nl/agent-chat.json +446 -0
- package/dist/locales/nl/agent-prompts.json +42 -0
- package/dist/locales/nl/agent-providers.json +131 -0
- package/dist/locales/nl/agent-usage.json +131 -0
- package/dist/locales.d.ts +5 -0
- package/dist/locales.js +20 -0
- package/dist/mounted-registry.d.ts +7 -0
- package/dist/mounted-registry.js +39 -0
- package/dist/port-transport.d.ts +6 -0
- package/dist/port-transport.js +129 -0
- package/dist/prompts/logic/focus.d.ts +2 -0
- package/dist/prompts/logic/focus.js +23 -0
- package/dist/prompts/logic/keys.d.ts +42 -0
- package/dist/prompts/logic/keys.js +94 -0
- package/dist/prompts/logic/prompts.d.ts +34 -0
- package/dist/prompts/logic/prompts.fixtures.d.ts +5 -0
- package/dist/prompts/logic/prompts.fixtures.js +79 -0
- package/dist/prompts/logic/prompts.js +32 -0
- package/dist/prompts/logic/subjects.d.ts +50 -0
- package/dist/prompts/logic/subjects.js +63 -0
- package/dist/prompts/logic/useFocusAfterAnswer.d.ts +6 -0
- package/dist/prompts/logic/useFocusAfterAnswer.js +28 -0
- package/dist/prompts/logic/usePromptSession.d.ts +24 -0
- package/dist/prompts/logic/usePromptSession.js +87 -0
- package/dist/prompts/ui/ApprovalBody.d.ts +13 -0
- package/dist/prompts/ui/ApprovalBody.js +34 -0
- package/dist/prompts/ui/PromptActions.d.ts +27 -0
- package/dist/prompts/ui/PromptActions.js +18 -0
- package/dist/prompts/ui/PromptCard.d.ts +16 -0
- package/dist/prompts/ui/PromptCard.js +50 -0
- package/dist/prompts/ui/PromptView.d.ts +2 -0
- package/dist/prompts/ui/PromptView.js +47 -0
- package/dist/prompts/ui/QuestionBody.d.ts +9 -0
- package/dist/prompts/ui/QuestionBody.js +87 -0
- package/dist/providers/AccountColors.d.ts +5 -0
- package/dist/providers/AccountColors.js +8 -0
- package/dist/providers/AccountDetail.d.ts +12 -0
- package/dist/providers/AccountDetail.js +69 -0
- package/dist/providers/AccountVariables.d.ts +8 -0
- package/dist/providers/AccountVariables.js +43 -0
- package/dist/providers/AddAccountForm.d.ts +10 -0
- package/dist/providers/AddAccountForm.js +54 -0
- package/dist/providers/CliDetail.d.ts +12 -0
- package/dist/providers/CliDetail.js +54 -0
- package/dist/providers/ProvidersPane.d.ts +9 -0
- package/dist/providers/ProvidersPane.js +86 -0
- package/dist/providers/account-actions.d.ts +7 -0
- package/dist/providers/account-actions.js +57 -0
- package/dist/providers/parts.d.ts +9 -0
- package/dist/providers/parts.js +18 -0
- package/dist/providers/provider-abilities.d.ts +2 -0
- package/dist/providers/provider-abilities.js +7 -0
- package/dist/providers/variables.d.ts +13 -0
- package/dist/providers/variables.js +63 -0
- package/dist/scope.d.ts +11 -0
- package/dist/scope.js +10 -0
- package/dist/settings/sections.d.ts +13 -0
- package/dist/settings/sections.js +32 -0
- package/dist/state/chats.d.ts +44 -0
- package/dist/state/chats.js +199 -0
- package/dist/state/provider-accounts.d.ts +17 -0
- package/dist/state/provider-accounts.js +67 -0
- package/dist/state/providers.d.ts +17 -0
- package/dist/state/providers.js +33 -0
- package/dist/state/usage.d.ts +44 -0
- package/dist/state/usage.js +121 -0
- package/dist/storage.d.ts +20 -0
- package/dist/storage.js +89 -0
- package/dist/theme.css +196 -0
- package/dist/transport.d.ts +24 -0
- package/dist/transport.js +18 -0
- package/dist/ui/PlainTextarea.d.ts +2 -0
- package/dist/ui/PlainTextarea.js +5 -0
- package/dist/usage/LimitsList.d.ts +18 -0
- package/dist/usage/LimitsList.js +73 -0
- package/dist/usage/UsageBreakdown.d.ts +9 -0
- package/dist/usage/UsageBreakdown.js +67 -0
- package/dist/usage/UsageChart.d.ts +10 -0
- package/dist/usage/UsageChart.js +47 -0
- package/dist/usage/UsageDialog.d.ts +6 -0
- package/dist/usage/UsageDialog.js +12 -0
- package/dist/usage/UsageLimits.d.ts +1 -0
- package/dist/usage/UsageLimits.js +57 -0
- package/dist/usage/UsageLimitsCard.d.ts +4 -0
- package/dist/usage/UsageLimitsCard.js +26 -0
- package/dist/usage/UsagePage.d.ts +7 -0
- package/dist/usage/UsagePage.js +154 -0
- package/dist/usage/UsagePane.d.ts +3 -0
- package/dist/usage/UsagePane.js +21 -0
- package/dist/usage/UsageSummary.d.ts +12 -0
- package/dist/usage/UsageSummary.js +15 -0
- package/dist/usage/UsageTiles.d.ts +7 -0
- package/dist/usage/UsageTiles.js +19 -0
- package/dist/usage/format.d.ts +13 -0
- package/dist/usage/format.js +55 -0
- package/dist/usage/limit-groups.d.ts +22 -0
- package/dist/usage/limit-groups.js +86 -0
- package/dist/usage/limits.d.ts +4 -0
- package/dist/usage/limits.js +58 -0
- package/dist/usage/money.d.ts +1 -0
- package/dist/usage/money.js +13 -0
- package/dist/usage/summary.d.ts +34 -0
- package/dist/usage/summary.js +94 -0
- package/package.json +895 -3
- package/src/agents/AccountDot.tsx +14 -0
- package/src/agents/AgentIcon.tsx +26 -0
- package/src/agents/ModelOptionControl.tsx +68 -0
- package/src/agents/ProviderLogo.tsx +20 -0
- package/src/agents/account-limits.ts +61 -0
- package/src/agents/accounts.ts +137 -0
- package/src/agents/model-name.ts +70 -0
- package/src/agents/model-options.ts +21 -0
- package/src/agents/provider-paths.ts +10 -0
- package/src/agents/status-look.ts +43 -0
- package/src/chat/account-choice.ts +44 -0
- package/src/chat/actions.ts +72 -0
- package/src/chat/activity.ts +8 -0
- package/src/chat/attachments.ts +118 -0
- package/src/chat/bookmarks.ts +87 -0
- package/src/chat/chat-client.ts +345 -0
- package/src/chat/chat-references.ts +11 -0
- package/src/chat/drafts.ts +165 -0
- package/src/chat/forks.ts +33 -0
- package/src/chat/guards.ts +84 -0
- package/src/chat/logic/ansi.ts +206 -0
- package/src/chat/logic/answers.ts +8 -0
- package/src/chat/logic/bookmarks.ts +42 -0
- package/src/chat/logic/context-usage.ts +36 -0
- package/src/chat/logic/fork.ts +200 -0
- package/src/chat/logic/handback.ts +10 -0
- package/src/chat/logic/json.ts +4 -0
- package/src/chat/logic/limit.ts +46 -0
- package/src/chat/logic/resume-compaction.ts +71 -0
- package/src/chat/logic/scrubber.ts +289 -0
- package/src/chat/logic/thread-cards.ts +64 -0
- package/src/chat/logic/timeline-copy.ts +65 -0
- package/src/chat/logic/timeline-target.ts +52 -0
- package/src/chat/logic/timeline.ts +419 -0
- package/src/chat/logic/tool-catalog.ts +62 -0
- package/src/chat/logic/tools.ts +102 -0
- package/src/chat/logic/welcome.ts +13 -0
- package/src/chat/logic/workflow.ts +66 -0
- package/src/chat/mentions.ts +232 -0
- package/src/chat/persisted-json.ts +67 -0
- package/src/chat/preferences.ts +179 -0
- package/src/chat/quote.ts +26 -0
- package/src/chat/recent-messages.ts +59 -0
- package/src/chat/resume-compaction-dismissals.ts +17 -0
- package/src/chat/runtime-modes.ts +18 -0
- package/src/chat/stash.ts +122 -0
- package/src/chat/subagent-conversation.ts +203 -0
- package/src/chat/subagent-list.ts +141 -0
- package/src/chat/subagent-support.ts +21 -0
- package/src/chat/subagent-view.ts +137 -0
- package/src/chat/thumbnails.ts +108 -0
- package/src/chat/timeline-flash.ts +33 -0
- package/src/chat/timeline-scroll.ts +199 -0
- package/src/chat/ui/AccountPill.tsx +31 -0
- package/src/chat/ui/AnsiOutput.tsx +18 -0
- package/src/chat/ui/BookmarkMarker.tsx +108 -0
- package/src/chat/ui/BookmarkSubmenu.tsx +53 -0
- package/src/chat/ui/ChatActivity.tsx +198 -0
- package/src/chat/ui/ChatReferenceChip.tsx +28 -0
- package/src/chat/ui/CodeBlock.tsx +175 -0
- package/src/chat/ui/Composer.tsx +1326 -0
- package/src/chat/ui/ComposerInput.tsx +187 -0
- package/src/chat/ui/DiffPool.tsx +36 -0
- package/src/chat/ui/EditDiff.tsx +30 -0
- package/src/chat/ui/FadingWords.tsx +18 -0
- package/src/chat/ui/ImageView.tsx +159 -0
- package/src/chat/ui/LimitState.tsx +165 -0
- package/src/chat/ui/Markdown.tsx +258 -0
- package/src/chat/ui/MessageActions.tsx +88 -0
- package/src/chat/ui/Pickers.tsx +272 -0
- package/src/chat/ui/PromptComposer.tsx +119 -0
- package/src/chat/ui/QuoteButton.tsx +103 -0
- package/src/chat/ui/ResumeCompactionDock.tsx +28 -0
- package/src/chat/ui/RunSettings.tsx +546 -0
- package/src/chat/ui/Scrubber.tsx +265 -0
- package/src/chat/ui/SubagentControls.tsx +66 -0
- package/src/chat/ui/SubagentInfo.tsx +67 -0
- package/src/chat/ui/SubagentStopButton.tsx +45 -0
- package/src/chat/ui/SubagentTimeline.tsx +170 -0
- package/src/chat/ui/Timeline.tsx +628 -0
- package/src/chat/ui/TimelineMenu.tsx +124 -0
- package/src/chat/ui/UnifiedDiff.tsx +99 -0
- package/src/chat/ui/UploadThumb.tsx +13 -0
- package/src/chat/ui/Welcome.tsx +22 -0
- package/src/chat/ui/chips.ts +15 -0
- package/src/chat/ui/code-lines.ts +60 -0
- package/src/chat/ui/code-streaming.ts +4 -0
- package/src/chat/ui/code-theme.ts +15 -0
- package/src/chat/ui/composer/chips.ts +43 -0
- package/src/chat/ui/composer/editor.ts +118 -0
- package/src/chat/ui/composer/insert.ts +22 -0
- package/src/chat/ui/composer/keys.ts +144 -0
- package/src/chat/ui/diff-theme.ts +23 -0
- package/src/chat/ui/file-links.ts +24 -0
- package/src/chat/ui/find-reveal.ts +23 -0
- package/src/chat/ui/full-diff.ts +25 -0
- package/src/chat/ui/icons.tsx +24 -0
- package/src/chat/ui/markdown-blocks.ts +123 -0
- package/src/chat/ui/quote-selection.ts +53 -0
- package/src/chat/ui/rehype-chips.ts +62 -0
- package/src/chat/ui/rehype-fade.ts +64 -0
- package/src/chat/ui/remark-html-as-text.ts +36 -0
- package/src/chat/ui/reply-context.ts +4 -0
- package/src/chat/ui/reveal.ts +135 -0
- package/src/chat/ui/rows/ForksRow.tsx +79 -0
- package/src/chat/ui/rows/MessageRows.tsx +415 -0
- package/src/chat/ui/rows/Rows.tsx +81 -0
- package/src/chat/ui/rows/SubagentRow.tsx +209 -0
- package/src/chat/ui/rows/TaskRow.tsx +91 -0
- package/src/chat/ui/rows/WorkRows.tsx +445 -0
- package/src/chat/ui/rows/WorkflowRow.tsx +120 -0
- package/src/chat/ui/rows/row-rhythm.ts +37 -0
- package/src/chat/ui/useToggleSet.ts +39 -0
- package/src/host.ts +324 -0
- package/src/lazy.tsx +64 -0
- package/src/locales/en/agent-chat.json +446 -0
- package/src/locales/en/agent-prompts.json +42 -0
- package/src/locales/en/agent-providers.json +131 -0
- package/src/locales/en/agent-usage.json +131 -0
- package/src/locales/nl/agent-chat.json +446 -0
- package/src/locales/nl/agent-prompts.json +42 -0
- package/src/locales/nl/agent-providers.json +131 -0
- package/src/locales/nl/agent-usage.json +131 -0
- package/src/locales.ts +33 -0
- package/src/mounted-registry.ts +45 -0
- package/src/port-transport.ts +146 -0
- package/src/prompts/logic/focus.ts +25 -0
- package/src/prompts/logic/keys.ts +116 -0
- package/src/prompts/logic/prompts.fixtures.ts +84 -0
- package/src/prompts/logic/prompts.ts +58 -0
- package/src/prompts/logic/subjects.ts +122 -0
- package/src/prompts/logic/useFocusAfterAnswer.ts +38 -0
- package/src/prompts/logic/usePromptSession.ts +114 -0
- package/src/prompts/ui/ApprovalBody.tsx +109 -0
- package/src/prompts/ui/PromptActions.tsx +109 -0
- package/src/prompts/ui/PromptCard.tsx +101 -0
- package/src/prompts/ui/PromptView.tsx +111 -0
- package/src/prompts/ui/QuestionBody.tsx +174 -0
- package/src/providers/AccountColors.tsx +8 -0
- package/src/providers/AccountDetail.tsx +209 -0
- package/src/providers/AccountVariables.tsx +126 -0
- package/src/providers/AddAccountForm.tsx +121 -0
- package/src/providers/CliDetail.tsx +157 -0
- package/src/providers/ProvidersPane.tsx +236 -0
- package/src/providers/account-actions.ts +67 -0
- package/src/providers/parts.tsx +25 -0
- package/src/providers/provider-abilities.ts +9 -0
- package/src/providers/variables.ts +87 -0
- package/src/scope.ts +30 -0
- package/src/settings/sections.ts +47 -0
- package/src/state/chats.ts +279 -0
- package/src/state/provider-accounts.ts +87 -0
- package/src/state/providers.ts +50 -0
- package/src/state/usage.ts +196 -0
- package/src/storage.ts +113 -0
- package/src/theme.css +196 -0
- package/src/transport.ts +50 -0
- package/src/ui/PlainTextarea.tsx +6 -0
- package/src/usage/LimitsList.tsx +232 -0
- package/src/usage/UsageBreakdown.tsx +210 -0
- package/src/usage/UsageChart.tsx +113 -0
- package/src/usage/UsageDialog.tsx +20 -0
- package/src/usage/UsageLimits.tsx +145 -0
- package/src/usage/UsageLimitsCard.tsx +36 -0
- package/src/usage/UsagePage.tsx +276 -0
- package/src/usage/UsagePane.tsx +52 -0
- package/src/usage/UsageSummary.tsx +50 -0
- package/src/usage/UsageTiles.tsx +34 -0
- package/src/usage/format.ts +71 -0
- package/src/usage/limit-groups.ts +119 -0
- package/src/usage/limits.ts +71 -0
- package/src/usage/money.ts +14 -0
- package/src/usage/summary.ts +137 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import type { AgentKind, ChatForkPayload, ChatInfo, ChatItem, ChatTurnItem, ModelSelection } from '@adecore/agent-contracts';
|
|
3
|
+
import type { TimelineRow } from './timeline';
|
|
4
|
+
|
|
5
|
+
/* The providers whose chat a machine can fork, and go on with in a fork, are the ones with a conversation the CLI keeps. */
|
|
6
|
+
export const FORKABLE_PROVIDERS: ReadonlySet<string> = new Set(['claude', 'codex']);
|
|
7
|
+
|
|
8
|
+
/* Where a fork goes on after, as the dialog words it. */
|
|
9
|
+
export interface ForkPoint {
|
|
10
|
+
turnId: string;
|
|
11
|
+
number: number;
|
|
12
|
+
total: number;
|
|
13
|
+
/* False while the client holds only the newest part of the thread, so `number` and `total` count from there. */
|
|
14
|
+
counted: boolean;
|
|
15
|
+
last: boolean;
|
|
16
|
+
/* The first line of what the person asked in that turn, or the label of a turn nobody typed. */
|
|
17
|
+
prompt: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function turnsOf(items: Record<string, ChatItem>, order: readonly string[]): ChatTurnItem[] {
|
|
21
|
+
return order.flatMap((id) => {
|
|
22
|
+
const item = items[id];
|
|
23
|
+
return item?.kind === 'turn' ? [item] : [];
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
* Why a chat cannot be forked after this turn right now, or null when it can. A turn still running,
|
|
29
|
+
* or any turn in the chat, is a transcript that grows while it would be copied.
|
|
30
|
+
*/
|
|
31
|
+
export function forkRefusal(info: ChatInfo | null, turn: ChatItem | undefined): string | null {
|
|
32
|
+
if (info === null || turn?.kind !== 'turn') {
|
|
33
|
+
return i18next.t('agent-chat:fork.refusal.notInConversation');
|
|
34
|
+
}
|
|
35
|
+
if (!FORKABLE_PROVIDERS.has(info.provider)) {
|
|
36
|
+
return i18next.t('agent-chat:fork.refusal.cliCannotFork');
|
|
37
|
+
}
|
|
38
|
+
if (info.agentSessionId === null) {
|
|
39
|
+
return i18next.t('agent-chat:fork.refusal.noConversation');
|
|
40
|
+
}
|
|
41
|
+
if (turn.state === 'running' || info.activeTurnId !== null) {
|
|
42
|
+
return i18next.t('agent-chat:fork.refusal.turnRunning');
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Why a fork cannot write a summary for its original right now, or null when it can. */
|
|
48
|
+
export function summaryRefusal(state: { busy: boolean; originalPresent: boolean }): string | null {
|
|
49
|
+
if (!state.originalPresent) {
|
|
50
|
+
return i18next.t('agent-chat:fork.refusal.originalGone');
|
|
51
|
+
}
|
|
52
|
+
if (state.busy) {
|
|
53
|
+
return i18next.t('agent-chat:fork.refusal.turnRunning');
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* The forks this client knows of that went on after this turn of this chat, in the order it holds them. */
|
|
59
|
+
export function forkIdsAfter(infos: Iterable<ChatInfo>, chatId: string, turnId: string): string[] {
|
|
60
|
+
const ids: string[] = [];
|
|
61
|
+
for (const info of infos) {
|
|
62
|
+
if (info.forkOf?.chatId === chatId && info.forkOf.turnId === turnId) {
|
|
63
|
+
ids.push(info.chatId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return ids;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* The turns of this chat that at least one fork this client knows of went on after. */
|
|
70
|
+
export function forkedTurnIds(infos: Iterable<ChatInfo>, chatId: string): Set<string> {
|
|
71
|
+
const turns = new Set<string>();
|
|
72
|
+
for (const info of infos) {
|
|
73
|
+
if (info.forkOf?.chatId === chatId) {
|
|
74
|
+
turns.add(info.forkOf.turnId);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return turns;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* The last turn that ended, which is where a fork from the node's menu goes on after. */
|
|
81
|
+
export function lastSettledTurn(items: Record<string, ChatItem>, order: readonly string[]): string | null {
|
|
82
|
+
return turnsOf(items, order).findLast((turn) => turn.state !== 'running')?.id ?? null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function forkPointOf(items: Record<string, ChatItem>, order: readonly string[], turnId: string, whole: boolean): ForkPoint | null {
|
|
86
|
+
const turns = turnsOf(items, order);
|
|
87
|
+
const index = turns.findIndex((turn) => turn.id === turnId);
|
|
88
|
+
const turn = turns[index];
|
|
89
|
+
if (turn === undefined) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const asked = order.map((id) => items[id]).find((item) => item?.kind === 'user' && item.turnId === turnId);
|
|
93
|
+
const text = asked?.kind === 'user' ? asked.text : (turn.label ?? '');
|
|
94
|
+
const prompt =
|
|
95
|
+
text
|
|
96
|
+
.split('\n')
|
|
97
|
+
.find((line) => line.trim() !== '')
|
|
98
|
+
?.trim() ?? null;
|
|
99
|
+
return { turnId, number: index + 1, total: turns.length, counted: whole, last: index === turns.length - 1, prompt };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* The line at the top of the dialog: which turn, and what it was about. */
|
|
103
|
+
export function forkPointLabel(point: ForkPoint, maxPrompt = 60): string {
|
|
104
|
+
const where = point.last
|
|
105
|
+
? i18next.t('agent-chat:fork.point.afterLast')
|
|
106
|
+
: point.counted
|
|
107
|
+
? i18next.t('agent-chat:fork.point.afterTurn', { number: point.number, total: point.total })
|
|
108
|
+
: i18next.t('agent-chat:fork.point.afterThis');
|
|
109
|
+
if (point.prompt === null) {
|
|
110
|
+
return where;
|
|
111
|
+
}
|
|
112
|
+
const prompt = point.prompt.length > maxPrompt ? `${point.prompt.slice(0, maxPrompt - 1)}…` : point.prompt;
|
|
113
|
+
return i18next.t('agent-chat:fork.point.about', { where, prompt });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* The turn a row of the thread belongs to, which is the turn "Fork from here" on that row goes on after. */
|
|
117
|
+
export function turnIdOfRow(row: TimelineRow): string | null {
|
|
118
|
+
switch (row.kind) {
|
|
119
|
+
case 'turn-start':
|
|
120
|
+
case 'turn-fold':
|
|
121
|
+
return row.turn.id;
|
|
122
|
+
case 'changed-files':
|
|
123
|
+
case 'forks':
|
|
124
|
+
return row.turnId;
|
|
125
|
+
case 'work':
|
|
126
|
+
case 'work-live':
|
|
127
|
+
case 'workflow':
|
|
128
|
+
return row.tool.turnId;
|
|
129
|
+
case 'work-group':
|
|
130
|
+
return row.tools[0]?.turnId ?? null;
|
|
131
|
+
case 'user':
|
|
132
|
+
case 'assistant':
|
|
133
|
+
case 'thinking':
|
|
134
|
+
case 'subagent':
|
|
135
|
+
case 'approval':
|
|
136
|
+
case 'question':
|
|
137
|
+
return row.item.turnId;
|
|
138
|
+
default:
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* What a fork becomes: a chat node on a canvas, or a chat view of its own in the sidebar. */
|
|
144
|
+
export type ForkShape = 'node' | 'view';
|
|
145
|
+
|
|
146
|
+
/* What the dialog offers, first the default: a view forks into a view, and a node into a node or, when asked, a view. */
|
|
147
|
+
export function forkShapes(origin: ForkShape): ForkShape[] {
|
|
148
|
+
return origin === 'view' ? ['view'] : ['node', 'view'];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* A worktree the fork works in, on this branch, with or without the files as they were after the turn. */
|
|
152
|
+
export interface ForkWorktreeChoice {
|
|
153
|
+
branch: string;
|
|
154
|
+
filesAfterTurn: boolean;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* The CLI, model and account a fork goes on with; only what differs from the original is sent. */
|
|
158
|
+
export interface ForkCliChoice {
|
|
159
|
+
provider: AgentKind;
|
|
160
|
+
selection: ModelSelection;
|
|
161
|
+
/* By id, the CLI's kind for its default account; absent leaves it to the machine. */
|
|
162
|
+
account?: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function forkPayload(input: {
|
|
166
|
+
chatId: string;
|
|
167
|
+
turnId: string;
|
|
168
|
+
title: string;
|
|
169
|
+
shape: ForkShape;
|
|
170
|
+
worktree?: ForkWorktreeChoice | null;
|
|
171
|
+
cli?: { original: ForkCliChoice; chosen: ForkCliChoice };
|
|
172
|
+
}): ChatForkPayload {
|
|
173
|
+
return {
|
|
174
|
+
chatId: input.chatId,
|
|
175
|
+
turnId: input.turnId,
|
|
176
|
+
title: input.title,
|
|
177
|
+
...(input.shape === 'view' ? { asView: true } : {}),
|
|
178
|
+
...(input.cli && input.cli.chosen.provider !== input.cli.original.provider ? { provider: input.cli.chosen.provider } : {}),
|
|
179
|
+
...(input.cli && (input.cli.chosen.provider !== input.cli.original.provider || input.cli.chosen.selection.model !== input.cli.original.selection.model)
|
|
180
|
+
? { selection: input.cli.chosen.selection }
|
|
181
|
+
: {}),
|
|
182
|
+
...(input.cli?.chosen.account !== undefined &&
|
|
183
|
+
(input.cli.chosen.provider !== input.cli.original.provider || input.cli.chosen.account !== input.cli.original.account)
|
|
184
|
+
? { account: input.cli.chosen.account }
|
|
185
|
+
: {}),
|
|
186
|
+
...(input.worktree ? { worktree: { branch: input.worktree.branch }, ...(input.worktree.filesAfterTurn ? { filesAfterTurn: true } : {}) } : {})
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* Why a branch cannot be the fork's, or null when it can. */
|
|
191
|
+
export function branchRefusal(branch: string, taken: readonly string[]): string | null {
|
|
192
|
+
const name = branch.trim();
|
|
193
|
+
if (name === '') {
|
|
194
|
+
return i18next.t('agent-chat:fork.branch.unnamed');
|
|
195
|
+
}
|
|
196
|
+
if (taken.includes(name)) {
|
|
197
|
+
return i18next.t('agent-chat:fork.branch.taken');
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ChatItem } from '@adecore/agent-contracts';
|
|
2
|
+
|
|
3
|
+
/* The report a `SubagentHandback` call carries in `input.message` (Claude Code 2.1.273), or null for any other item. */
|
|
4
|
+
export function handbackReportOf(item: ChatItem): string | null {
|
|
5
|
+
if (item.kind !== 'tool' || item.name !== 'SubagentHandback' || typeof item.input !== 'object' || item.input === null) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const message = (item.input as { message?: unknown }).message;
|
|
9
|
+
return typeof message === 'string' && message.trim() !== '' ? message : null;
|
|
10
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import type { ChatInfo } from '@adecore/agent-contracts';
|
|
3
|
+
import { formatMoment } from '@adecore/ui/format';
|
|
4
|
+
|
|
5
|
+
export interface LimitView {
|
|
6
|
+
/* The state in a word or two with its time, for a pill in a header. */
|
|
7
|
+
pill: string;
|
|
8
|
+
title: string;
|
|
9
|
+
/* When it goes on, or what a person can do; null when the CLI named no time and nothing is owed. */
|
|
10
|
+
detail: string | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* What a chat that stopped on a limit says about it, from its info alone, so a header that never holds
|
|
15
|
+
* the thread says the same as the chat. Null while the last turn stopped on nothing, or a turn runs.
|
|
16
|
+
*/
|
|
17
|
+
export function limitView(info: Pick<ChatInfo, 'limit' | 'resumeAt' | 'activeTurnId'>, now: number): LimitView | null {
|
|
18
|
+
const limit = info.limit;
|
|
19
|
+
if (limit === undefined || info.activeTurnId !== null) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const resumes = info.resumeAt === undefined ? null : formatMoment(info.resumeAt, now);
|
|
23
|
+
if (limit.kind === 'overload') {
|
|
24
|
+
return {
|
|
25
|
+
pill: resumes === null ? i18next.t('agent-chat:limit.overload.pill') : i18next.t('agent-chat:limit.overload.pillRetry', { time: resumes }),
|
|
26
|
+
title: i18next.t('agent-chat:limit.overload.title'),
|
|
27
|
+
detail: resumes === null ? i18next.t('agent-chat:limit.overload.sendAgain') : i18next.t('agent-chat:limit.overload.retries', { time: resumes })
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const resets = limit.resetsAt === undefined ? null : formatMoment(limit.resetsAt, now);
|
|
31
|
+
return {
|
|
32
|
+
pill:
|
|
33
|
+
resumes !== null
|
|
34
|
+
? i18next.t('agent-chat:limit.usage.pillResumes', { time: resumes })
|
|
35
|
+
: resets !== null
|
|
36
|
+
? i18next.t('agent-chat:limit.usage.pillUntil', { time: resets })
|
|
37
|
+
: i18next.t('agent-chat:limit.usage.pill'),
|
|
38
|
+
title: i18next.t('agent-chat:limit.usage.title'),
|
|
39
|
+
detail:
|
|
40
|
+
resumes !== null
|
|
41
|
+
? i18next.t('agent-chat:limit.usage.resumes', { time: resumes })
|
|
42
|
+
: resets !== null
|
|
43
|
+
? i18next.t('agent-chat:limit.usage.resets', { time: resets })
|
|
44
|
+
: null
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ChatInfo, ChatItem, ProviderCapabilities } from '@adecore/agent-contracts';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* An absolute count, not a share of the window: reading a thread again costs per token, so 100k in a
|
|
5
|
+
* window of 1M is as expensive as 100k in a window of 200k.
|
|
6
|
+
*/
|
|
7
|
+
export const RESUME_COMPACTION_TOKENS = 100_000;
|
|
8
|
+
|
|
9
|
+
/* The longest prompt cache Claude keeps is an hour, so past this the whole thread is read again anyway. */
|
|
10
|
+
export const RESUME_COMPACTION_IDLE_MS = 70 * 60_000;
|
|
11
|
+
|
|
12
|
+
export interface ResumeCompactionOffer {
|
|
13
|
+
/* The turn the offer is about, and the key "Keep full history" is remembered under. */
|
|
14
|
+
turnId: string;
|
|
15
|
+
tokens: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function lastTurn(items: readonly ChatItem[]): Extract<ChatItem, { kind: 'turn' }> | null {
|
|
19
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
20
|
+
const item = items[i]!;
|
|
21
|
+
if (item.kind === 'turn') {
|
|
22
|
+
return item;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* Whether to offer compacting before the next message. The caller passes `now`, so this stays pure
|
|
30
|
+
* and the test needs no clock.
|
|
31
|
+
*/
|
|
32
|
+
export function resumeCompactionOffer({
|
|
33
|
+
info,
|
|
34
|
+
compaction,
|
|
35
|
+
items,
|
|
36
|
+
dismissedTurnId,
|
|
37
|
+
now
|
|
38
|
+
}: {
|
|
39
|
+
info: ChatInfo;
|
|
40
|
+
/* Absent until the host answered `provider.list`. */
|
|
41
|
+
compaction: ProviderCapabilities['compaction'] | undefined;
|
|
42
|
+
items: readonly ChatItem[];
|
|
43
|
+
dismissedTurnId: string | null;
|
|
44
|
+
now: number;
|
|
45
|
+
}): ResumeCompactionOffer | null {
|
|
46
|
+
if (compaction === undefined || compaction === 'none') {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (info.activeTurnId !== null || (info.queue ?? []).length > 0) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
if (info.usage.contextTokens < RESUME_COMPACTION_TOKENS) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const turn = lastTurn(items);
|
|
56
|
+
if (turn === null || turn.endedAt === null || turn.id === dismissedTurnId) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
if (now - turn.endedAt < RESUME_COMPACTION_IDLE_MS) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
/*
|
|
63
|
+
* `contextTokens` keeps the old number until the next request, so without this the offer would
|
|
64
|
+
* come straight back after a compaction. A native compaction is a turn without a message of the
|
|
65
|
+
* person, which is why this looks at the turn and not at the last thing that was said.
|
|
66
|
+
*/
|
|
67
|
+
if (items.some((item) => item.kind === 'compaction' && item.turnId === turn.id)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return { turnId: turn.id, tokens: info.usage.contextTokens };
|
|
71
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import type { ChatBookmark } from '@adecore/agent-contracts';
|
|
2
|
+
import type { TimelineRow } from './timeline';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* The strip at the left edge of a chat view with a tick per message a person sent, as numbers: which
|
|
6
|
+
* rows get a tick, where the ticks go in the height the strip has, which messages are on screen, and
|
|
7
|
+
* which one a pointer means. The component only draws what comes out of here.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/* A message the person sent, a turn the machine opened with the results of tasks, or any other message with a bookmark on it. */
|
|
11
|
+
export type TickKind = 'person' | 'wake' | 'bookmark';
|
|
12
|
+
|
|
13
|
+
export interface ScrubberTick {
|
|
14
|
+
id: string;
|
|
15
|
+
/* The row of the timeline the tick jumps to. */
|
|
16
|
+
rowIndex: number;
|
|
17
|
+
kind: TickKind;
|
|
18
|
+
text: string;
|
|
19
|
+
createdAt: number;
|
|
20
|
+
/* The turn the message opened, which is the turn a fork from the card goes on after. */
|
|
21
|
+
turnId: string | null;
|
|
22
|
+
bookmark: ChatBookmark | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* With fewer messages than this the thread is short enough to scroll through, and the strip is noise. */
|
|
26
|
+
export const SCRUBBER_MIN_TICKS = 3;
|
|
27
|
+
|
|
28
|
+
export const TICK_HEIGHT_PX = 2;
|
|
29
|
+
|
|
30
|
+
const PITCH_PX = 8;
|
|
31
|
+
const MIN_PITCH_PX = 4;
|
|
32
|
+
|
|
33
|
+
/* The thread's content column in a view of its own (`.chat-column-content`) and the scroller's padding around it (`px-4`). */
|
|
34
|
+
const CONTENT_MAX_WIDTH_PX = 768;
|
|
35
|
+
const THREAD_PADDING_PX = 16;
|
|
36
|
+
export const STRIP_WIDTH_PX = 24;
|
|
37
|
+
/* The strip's distance from the view's left edge, and the air it keeps between itself and the thread's text. */
|
|
38
|
+
export const STRIP_INSET_PX = 8;
|
|
39
|
+
const STRIP_CLEARANCE_PX = STRIP_INSET_PX + STRIP_WIDTH_PX + STRIP_INSET_PX;
|
|
40
|
+
|
|
41
|
+
const WIDTH_HOVERED_PX = 16;
|
|
42
|
+
const WIDTH_NEAR_PERSON_PX = 12;
|
|
43
|
+
const WIDTH_NEAR_WAKE_PX = 8;
|
|
44
|
+
const WIDTH_PERSON_PX = 8;
|
|
45
|
+
const WIDTH_WAKE_PX = 5;
|
|
46
|
+
|
|
47
|
+
/* `marks` is what `bookmarkRows` answers: the bookmark drawn at a row, by row id. */
|
|
48
|
+
export function ticksOf(rows: readonly TimelineRow[], marks: ReadonlyMap<string, ChatBookmark> = new Map()): ScrubberTick[] {
|
|
49
|
+
return rows.flatMap((row, rowIndex): ScrubberTick[] => {
|
|
50
|
+
const bookmark = marks.get(row.id) ?? null;
|
|
51
|
+
if (row.kind === 'user') {
|
|
52
|
+
// A message of only attachments still has a place in the thread; its names stand in for the text.
|
|
53
|
+
const text = row.item.text !== '' ? row.item.text : (row.item.attachments ?? []).map((attachment) => attachment.name).join(', ');
|
|
54
|
+
return [{ id: row.id, rowIndex, kind: 'person', text, createdAt: row.item.createdAt, turnId: row.item.turnId, bookmark }];
|
|
55
|
+
}
|
|
56
|
+
// The other turns nobody asked for (a sub-agent that finished, the CLI going on) are no place to return to.
|
|
57
|
+
if (row.kind === 'turn-start' && ((row.turn.taskIds?.length ?? 0) > 0 || (row.turn.messageFrom?.length ?? 0) > 0)) {
|
|
58
|
+
return [{ id: row.id, rowIndex, kind: 'wake', text: row.label, createdAt: row.turn.createdAt, turnId: row.turn.id, bookmark: null }];
|
|
59
|
+
}
|
|
60
|
+
if (bookmark === null) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
if (row.kind === 'assistant') {
|
|
64
|
+
return [{ id: row.id, rowIndex, kind: 'bookmark', text: row.item.text, createdAt: row.item.createdAt, turnId: row.item.turnId, bookmark }];
|
|
65
|
+
}
|
|
66
|
+
// The fold of a turn a marked reply is hidden in; the card reads the start the bookmark kept.
|
|
67
|
+
const turnId = row.kind === 'turn-fold' ? row.turn.id : null;
|
|
68
|
+
return [{ id: row.id, rowIndex, kind: 'bookmark', text: bookmark.excerpt, createdAt: bookmark.createdAt, turnId, bookmark }];
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* One drawn tick: a message, or a run of messages merged once the height has fewer pixels than they need. */
|
|
73
|
+
export interface TickSlot {
|
|
74
|
+
first: number;
|
|
75
|
+
last: number;
|
|
76
|
+
kind: TickKind;
|
|
77
|
+
/* Whether a message under it has a bookmark, which draws it in the accent. */
|
|
78
|
+
marked: boolean;
|
|
79
|
+
/* Whether a find hit falls under it, which puts a mark of the find's own color beside it. */
|
|
80
|
+
found: boolean;
|
|
81
|
+
/* The top of the tick inside the strip. */
|
|
82
|
+
y: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ScrubberLayout {
|
|
86
|
+
top: number;
|
|
87
|
+
pitch: number;
|
|
88
|
+
count: number;
|
|
89
|
+
slots: TickSlot[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* `marked` and `found` say per message whether it has a bookmark and whether a find hit falls under it; absent, none has. */
|
|
93
|
+
export function layoutTicks(kinds: readonly TickKind[], height: number, marked: readonly boolean[] = [], found: readonly boolean[] = []): ScrubberLayout {
|
|
94
|
+
const count = kinds.length;
|
|
95
|
+
const room = Math.max(0, Math.floor(height));
|
|
96
|
+
const slotCount = Math.min(count, Math.floor(room / MIN_PITCH_PX));
|
|
97
|
+
if (slotCount === 0) {
|
|
98
|
+
return { top: 0, pitch: MIN_PITCH_PX, count, slots: [] };
|
|
99
|
+
}
|
|
100
|
+
const pitch = Math.min(PITCH_PX, Math.floor(room / slotCount));
|
|
101
|
+
const top = Math.floor((room - slotCount * pitch) / 2);
|
|
102
|
+
const inset = Math.floor((pitch - TICK_HEIGHT_PX) / 2);
|
|
103
|
+
const slots: TickSlot[] = [];
|
|
104
|
+
for (let i = 0; i < slotCount; i++) {
|
|
105
|
+
const first = Math.floor((i * count) / slotCount);
|
|
106
|
+
const last = Math.floor(((i + 1) * count) / slotCount) - 1;
|
|
107
|
+
// A merged tick is the person's as soon as one message in it is, since those are what the strip is for.
|
|
108
|
+
const run = kinds.slice(first, last + 1);
|
|
109
|
+
const kind = run.includes('person') ? 'person' : run.includes('bookmark') ? 'bookmark' : 'wake';
|
|
110
|
+
slots.push({
|
|
111
|
+
first,
|
|
112
|
+
last,
|
|
113
|
+
kind,
|
|
114
|
+
marked: marked.slice(first, last + 1).includes(true),
|
|
115
|
+
found: found.slice(first, last + 1).includes(true),
|
|
116
|
+
y: top + i * pitch + inset
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return { top, pitch, count, slots };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/*
|
|
123
|
+
* The tick whose stretch of the thread a row falls in: a tick stands for its row and every row up to
|
|
124
|
+
* the next tick's, and a row above the first tick counts under the first. Null without ticks.
|
|
125
|
+
*/
|
|
126
|
+
export function tickOfRow(ticks: readonly ScrubberTick[], rowIndex: number): number | null {
|
|
127
|
+
if (ticks.length === 0) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
let low = 0;
|
|
131
|
+
let high = ticks.length - 1;
|
|
132
|
+
let found = 0;
|
|
133
|
+
while (low <= high) {
|
|
134
|
+
const middle = (low + high) >> 1;
|
|
135
|
+
if (ticks[middle]!.rowIndex <= rowIndex) {
|
|
136
|
+
found = middle;
|
|
137
|
+
low = middle + 1;
|
|
138
|
+
} else {
|
|
139
|
+
high = middle - 1;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return found;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Per tick, whether any of the rows has a find hit in its stretch. */
|
|
146
|
+
export function ticksWithHits(ticks: readonly ScrubberTick[], hitRows: readonly number[]): boolean[] {
|
|
147
|
+
const found = ticks.map(() => false);
|
|
148
|
+
for (const row of hitRows) {
|
|
149
|
+
const tick = tickOfRow(ticks, row);
|
|
150
|
+
if (tick !== null) {
|
|
151
|
+
found[tick] = true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return found;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* The message under a height in the strip; inside a merged tick the height picks among its messages. */
|
|
158
|
+
export function messageAt(layout: ScrubberLayout, y: number): number | null {
|
|
159
|
+
const { slots, top, pitch } = layout;
|
|
160
|
+
if (slots.length === 0) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const offset = y - top;
|
|
164
|
+
const index = Math.min(slots.length - 1, Math.max(0, Math.floor(offset / pitch)));
|
|
165
|
+
const slot = slots[index]!;
|
|
166
|
+
const fraction = Math.min(1, Math.max(0, (offset - index * pitch) / pitch));
|
|
167
|
+
return slot.first + Math.min(slot.last - slot.first, Math.floor(fraction * (slot.last - slot.first + 1)));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function slotOf(layout: ScrubberLayout, message: number): number | null {
|
|
171
|
+
const { slots } = layout;
|
|
172
|
+
let low = 0;
|
|
173
|
+
let high = slots.length - 1;
|
|
174
|
+
while (low <= high) {
|
|
175
|
+
const middle = (low + high) >> 1;
|
|
176
|
+
const slot = slots[middle]!;
|
|
177
|
+
if (message < slot.first) {
|
|
178
|
+
high = middle - 1;
|
|
179
|
+
} else if (message > slot.last) {
|
|
180
|
+
low = middle + 1;
|
|
181
|
+
} else {
|
|
182
|
+
return middle;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/*
|
|
189
|
+
* At rest every tick of a kind is as long as the next, so length never competes with the brightness
|
|
190
|
+
* that says what is on screen. Only a pointer on the strip magnifies, the way a dock does, one step out.
|
|
191
|
+
*/
|
|
192
|
+
export function tickWidth(kind: TickKind, distanceFromPointer: number | null): number {
|
|
193
|
+
if (distanceFromPointer === 0) {
|
|
194
|
+
return WIDTH_HOVERED_PX;
|
|
195
|
+
}
|
|
196
|
+
if (distanceFromPointer === 1) {
|
|
197
|
+
return kind === 'wake' ? WIDTH_NEAR_WAKE_PX : WIDTH_NEAR_PERSON_PX;
|
|
198
|
+
}
|
|
199
|
+
return kind === 'wake' ? WIDTH_WAKE_PX : WIDTH_PERSON_PX;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* The last index whose start lies at or above a line, or -1. The starts only grow. */
|
|
203
|
+
function lastAtOrAbove(starts: readonly number[], line: number): number {
|
|
204
|
+
let low = 0;
|
|
205
|
+
let high = starts.length - 1;
|
|
206
|
+
let found = -1;
|
|
207
|
+
while (low <= high) {
|
|
208
|
+
const middle = (low + high) >> 1;
|
|
209
|
+
if (starts[middle]! <= line) {
|
|
210
|
+
found = middle;
|
|
211
|
+
low = middle + 1;
|
|
212
|
+
} else {
|
|
213
|
+
high = middle - 1;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return found;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface ReadingPosition {
|
|
220
|
+
/* Where each message's row starts in the thread, in the virtualizer's measurements. */
|
|
221
|
+
starts: readonly number[];
|
|
222
|
+
scrollTop: number;
|
|
223
|
+
/* The height of the thread a person can read, without what the composer covers. */
|
|
224
|
+
visibleHeight: number;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export interface MessageRange {
|
|
228
|
+
first: number;
|
|
229
|
+
last: number;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/*
|
|
233
|
+
* The messages on screen. A message counts from its own start to the start of the next one, so the
|
|
234
|
+
* question whose answer fills the screen is in view even after it scrolled out at the top.
|
|
235
|
+
*/
|
|
236
|
+
export function messagesInView({ starts, scrollTop, visibleHeight }: ReadingPosition): MessageRange | null {
|
|
237
|
+
const bottom = scrollTop + visibleHeight;
|
|
238
|
+
if (starts.length === 0 || visibleHeight <= 0 || starts[0]! >= bottom) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const first = Math.max(0, lastAtOrAbove(starts, scrollTop));
|
|
242
|
+
let last = lastAtOrAbove(starts, bottom);
|
|
243
|
+
// A row that starts on the very last line has not a pixel on screen yet.
|
|
244
|
+
if (starts[last]! >= bottom) {
|
|
245
|
+
last--;
|
|
246
|
+
}
|
|
247
|
+
return { first, last: Math.max(first, last) };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* Whether a drawn tick covers any of the messages on screen. */
|
|
251
|
+
export function slotInView(slot: TickSlot, range: MessageRange | null): boolean {
|
|
252
|
+
return range !== null && slot.last >= range.first && slot.first <= range.last;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/*
|
|
256
|
+
* The message a step back or forward lands on, among the ones `eligible` allows. A jump puts a
|
|
257
|
+
* message's start at the top, so back from there is the one before it, and back from the middle of
|
|
258
|
+
* a long answer is the question it belongs to. A pixel of slack absorbs rounding in the scroller.
|
|
259
|
+
*/
|
|
260
|
+
export function stepMessage(starts: readonly number[], eligible: (index: number) => boolean, scrollTop: number, direction: -1 | 1): number | null {
|
|
261
|
+
if (direction === -1) {
|
|
262
|
+
for (let i = lastAtOrAbove(starts, scrollTop - 1.5); i >= 0; i--) {
|
|
263
|
+
if (eligible(i)) {
|
|
264
|
+
return i;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
for (let i = lastAtOrAbove(starts, scrollTop + 1) + 1; i < starts.length; i++) {
|
|
270
|
+
if (eligible(i)) {
|
|
271
|
+
return i;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/*
|
|
278
|
+
* The left padding of the thread in a view that draws the strip at its left edge. A wide view keeps
|
|
279
|
+
* the usual padding, since its centered column already clears the strip; a narrower one pads until
|
|
280
|
+
* the column's text starts clear of it, so nothing is ever drawn under the strip.
|
|
281
|
+
*/
|
|
282
|
+
export function threadPaddingLeft(width: number, strip: boolean): number {
|
|
283
|
+
if (!strip) {
|
|
284
|
+
return THREAD_PADDING_PX;
|
|
285
|
+
}
|
|
286
|
+
// The column's margin is half of what the padding leaves, so the padding counts the clearance twice.
|
|
287
|
+
const padding = 2 * STRIP_CLEARANCE_PX + CONTENT_MAX_WIDTH_PX + THREAD_PADDING_PX - Math.floor(width);
|
|
288
|
+
return Math.min(STRIP_CLEARANCE_PX, Math.max(THREAD_PADDING_PX, padding));
|
|
289
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ThreadCard } from '../../host';
|
|
2
|
+
import type { TimelineRow } from './timeline';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* When what a row shows began, or null for a row that only follows the one before it: a turn's
|
|
6
|
+
* changed files and forks, a note, a report. The working row closes the thread, so every card goes
|
|
7
|
+
* above it.
|
|
8
|
+
*/
|
|
9
|
+
function rowTime(row: TimelineRow): number | null {
|
|
10
|
+
switch (row.kind) {
|
|
11
|
+
case 'user':
|
|
12
|
+
case 'assistant':
|
|
13
|
+
case 'thinking':
|
|
14
|
+
case 'approval':
|
|
15
|
+
case 'question':
|
|
16
|
+
case 'subagent':
|
|
17
|
+
return row.item.createdAt;
|
|
18
|
+
case 'turn-start':
|
|
19
|
+
case 'turn-fold':
|
|
20
|
+
return row.turn.createdAt;
|
|
21
|
+
case 'work':
|
|
22
|
+
case 'work-live':
|
|
23
|
+
case 'workflow':
|
|
24
|
+
return row.tool.createdAt;
|
|
25
|
+
case 'work-group':
|
|
26
|
+
return row.tools[0]?.createdAt ?? null;
|
|
27
|
+
case 'working':
|
|
28
|
+
return Number.POSITIVE_INFINITY;
|
|
29
|
+
default:
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Prefixed, so a card never shares a key with a row of the chat's own. */
|
|
35
|
+
function cardRow(card: ThreadCard): TimelineRow {
|
|
36
|
+
return { kind: 'app-card', id: `app-card-${card.id}`, card };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
* The thread's rows with an app's cards among them: a card goes right before the first row that
|
|
41
|
+
* began after it, so in a folded turn it lands under the fold, and in an open one between its calls.
|
|
42
|
+
* Without cards the rows come back as they are, the same array.
|
|
43
|
+
*/
|
|
44
|
+
export function withThreadCards(rows: TimelineRow[], cards: readonly ThreadCard[]): TimelineRow[] {
|
|
45
|
+
if (cards.length === 0) {
|
|
46
|
+
return rows;
|
|
47
|
+
}
|
|
48
|
+
// Sorting is stable, so cards of the same moment keep the order the app gave them.
|
|
49
|
+
const pending = [...cards].sort((first, second) => first.at - second.at);
|
|
50
|
+
const merged: TimelineRow[] = [];
|
|
51
|
+
let next = 0;
|
|
52
|
+
for (const row of rows) {
|
|
53
|
+
const time = rowTime(row);
|
|
54
|
+
while (time !== null && next < pending.length && pending[next]!.at < time) {
|
|
55
|
+
merged.push(cardRow(pending[next]!));
|
|
56
|
+
next += 1;
|
|
57
|
+
}
|
|
58
|
+
merged.push(row);
|
|
59
|
+
}
|
|
60
|
+
for (; next < pending.length; next++) {
|
|
61
|
+
merged.push(cardRow(pending[next]!));
|
|
62
|
+
}
|
|
63
|
+
return merged;
|
|
64
|
+
}
|