ethagent 3.3.3 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/.claude-plugin/marketplace.json +11 -0
  2. package/.claude-plugin/plugin.json +35 -0
  3. package/LICENSE +1 -1
  4. package/README.md +64 -104
  5. package/commands/ethagent.md +40 -0
  6. package/package.json +16 -16
  7. package/src/app/keybindings/KeybindingProvider.tsx +1 -6
  8. package/src/app/keybindings/types.ts +1 -6
  9. package/src/cli/ResetConfirmView.tsx +54 -53
  10. package/src/cli/demo.ts +86 -0
  11. package/src/cli/hookIo.ts +45 -0
  12. package/src/cli/main.tsx +94 -123
  13. package/src/cli/memoryGuard.ts +49 -0
  14. package/src/cli/reset.ts +28 -70
  15. package/src/cli/sessionStart.ts +33 -0
  16. package/src/cli/status.ts +46 -0
  17. package/src/cli/sync.ts +167 -0
  18. package/src/cli/syncAdapters/claude-code.ts +86 -0
  19. package/src/cli/syncAdapters/codex.ts +66 -0
  20. package/src/cli/syncAdapters/index.ts +45 -0
  21. package/src/cli/syncAdapters/managedBlock.ts +175 -0
  22. package/src/cli/syncAdapters/shared.ts +63 -0
  23. package/src/identity/continuity/envelopeParse.ts +20 -1
  24. package/src/identity/continuity/publicSkills.ts +3 -1
  25. package/src/identity/continuity/skills/publicSkillsSync.ts +2 -1
  26. package/src/identity/continuity/skills/scaffold.ts +5 -2
  27. package/src/identity/continuity/snapshots.ts +12 -5
  28. package/src/identity/continuity/storage/defaults.ts +20 -19
  29. package/src/identity/continuity/storage/status.ts +1 -1
  30. package/src/identity/ens/ensLookup/constants.ts +1 -1
  31. package/src/identity/manager/IdentityManager.tsx +33 -0
  32. package/src/identity/{hub → manager}/OperationalRoutes.tsx +37 -18
  33. package/src/identity/{hub → manager}/Routes.tsx +48 -34
  34. package/src/identity/{hub → manager}/continuity/ContinuityDashboardScreen.tsx +9 -19
  35. package/src/identity/{hub → manager}/continuity/RebackupStorageScreen.tsx +3 -3
  36. package/src/identity/manager/continuity/RecoveryConfirmScreen.tsx +102 -0
  37. package/src/identity/{hub → manager}/continuity/SavePromptScreen.tsx +2 -3
  38. package/src/identity/{hub → manager}/continuity/completion.ts +1 -1
  39. package/src/identity/{hub → manager}/continuity/effects.ts +1 -1
  40. package/src/identity/{hub → manager}/continuity/skills/DeleteSkillConfirmScreen.tsx +2 -2
  41. package/src/identity/{hub → manager}/continuity/skills/NewSkillScreen.tsx +0 -5
  42. package/src/identity/{hub → manager}/continuity/skills/NewSkillVisibilityScreen.tsx +4 -4
  43. package/src/identity/{hub → manager}/continuity/skills/SkillActionsScreen.tsx +6 -22
  44. package/src/identity/{hub → manager}/continuity/skills/SkillsTreeScreen.tsx +5 -17
  45. package/src/identity/{hub → manager}/continuity/snapshot.ts +1 -1
  46. package/src/identity/{hub → manager}/continuity/vault.ts +1 -1
  47. package/src/identity/{hub → manager}/create/CreateFlow.tsx +59 -32
  48. package/src/identity/{hub → manager}/create/effects.ts +19 -10
  49. package/src/identity/manager/create/importScan.ts +122 -0
  50. package/src/identity/{hub → manager}/custody/CustodyEditFlow.tsx +17 -61
  51. package/src/identity/{hub → manager}/custody/actions.ts +1 -15
  52. package/src/identity/{hub → manager}/custody/routes.tsx +20 -40
  53. package/src/identity/{hub → manager}/custody/transactions.ts +1 -0
  54. package/src/identity/{hub → manager}/custody/types.ts +1 -2
  55. package/src/identity/{hub → manager}/custody/useCustodyEffects.ts +1 -1
  56. package/src/identity/{hub → manager}/ens/EnsEditAdvancedScreens.tsx +2 -2
  57. package/src/identity/{hub → manager}/ens/EnsEditMaintenanceScreens.tsx +12 -23
  58. package/src/identity/{hub → manager}/ens/EnsEditReviewScreens.tsx +18 -42
  59. package/src/identity/{hub → manager}/ens/EnsEditRunners.tsx +1 -1
  60. package/src/identity/{hub → manager}/ens/EnsEditShared.tsx +0 -2
  61. package/src/identity/{hub → manager}/ens/EnsEditSimpleScreens.tsx +10 -19
  62. package/src/identity/{hub → manager}/ens/EnsFlow.tsx +133 -41
  63. package/src/identity/{hub → manager}/ens/EnsOperatorWalletsScreen.tsx +14 -19
  64. package/src/identity/{hub → manager}/ens/editCopy.ts +1 -14
  65. package/src/identity/{hub → manager}/profile/EditProfileFlow.tsx +99 -66
  66. package/src/identity/{hub → manager}/profile/effects.ts +1 -3
  67. package/src/identity/{hub → manager}/profile/operatorSave.ts +1 -1
  68. package/src/identity/{hub → manager}/profile/state.ts +1 -1
  69. package/src/identity/{hub/identityHubReducer.ts → manager/reducer.ts} +25 -26
  70. package/src/identity/{hub → manager}/restore/RestoreFlow.tsx +16 -24
  71. package/src/identity/{hub → manager}/restore/apply.ts +1 -1
  72. package/src/identity/{hub → manager}/restore/auth.ts +1 -1
  73. package/src/identity/{hub → manager}/restore/discover.ts +1 -1
  74. package/src/identity/{hub → manager}/restore/fetch.ts +1 -1
  75. package/src/identity/{hub → manager}/restore/restoreAdmin.ts +1 -1
  76. package/src/identity/{hub → manager}/restore/useRestoreEffects.ts +2 -9
  77. package/src/identity/{hub → manager}/settings/StorageCredentialScreen.tsx +10 -25
  78. package/src/identity/{hub → manager}/shared/components/DetailsScreen.tsx +5 -7
  79. package/src/identity/{hub → manager}/shared/components/ErrorScreen.tsx +6 -10
  80. package/src/identity/{hub → manager}/shared/components/FlowTimeline.tsx +4 -3
  81. package/src/identity/{hub → manager}/shared/components/IdentitySummary.tsx +19 -59
  82. package/src/identity/manager/shared/components/LazyMenu.tsx +147 -0
  83. package/src/identity/manager/shared/components/MenuScreen.tsx +220 -0
  84. package/src/identity/manager/shared/components/OperationCompleteScreen.tsx +28 -0
  85. package/src/identity/{hub → manager}/shared/components/UnlinkedIdentityScreen.tsx +9 -10
  86. package/src/identity/{hub → manager}/shared/components/WalletApprovalScreen.tsx +1 -2
  87. package/src/identity/manager/shared/components/Wordmark.tsx +54 -0
  88. package/src/identity/{hub → manager}/shared/components/menuFlagsFromReconciliation.ts +39 -15
  89. package/src/identity/{hub → manager}/shared/effects/profilePrep.ts +1 -1
  90. package/src/identity/manager/shared/effects/types.ts +30 -0
  91. package/src/identity/{hub → manager}/shared/model/copy.ts +0 -4
  92. package/src/identity/{hub → manager}/shared/model/errors.ts +32 -3
  93. package/src/identity/{hub → manager}/shared/model/network.ts +2 -2
  94. package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/hook.ts +5 -0
  95. package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/run.ts +1 -1
  96. package/src/identity/{hub/shared/reconciliation/useAgentReconciliation.ts → manager/shared/reconciliation/index.ts} +6 -0
  97. package/src/identity/{hub → manager}/shared/utils.ts +6 -10
  98. package/src/identity/{hub → manager}/transfer/TokenTransferFlow.tsx +3 -3
  99. package/src/identity/{hub → manager}/transfer/TokenTransferScreens.tsx +4 -10
  100. package/src/identity/{hub → manager}/transfer/effects.ts +1 -1
  101. package/src/identity/{hub → manager}/types.ts +5 -6
  102. package/src/identity/{hub/useIdentityHubContinuity.ts → manager/useContinuity.ts} +59 -27
  103. package/src/identity/{hub/useIdentityHubController.ts → manager/useController.ts} +38 -35
  104. package/src/identity/{hub/useIdentityHubSideEffects.ts → manager/useSideEffects.ts} +40 -4
  105. package/src/identity/registry/erc8004/discovery.ts +3 -17
  106. package/src/identity/registry/erc8004/utils.ts +1 -1
  107. package/src/identity/storage/ipfs.ts +21 -1
  108. package/src/identity/wallet/browserWallet/html.ts +10 -2
  109. package/src/identity/wallet/browserWallet/http.ts +18 -0
  110. package/src/identity/wallet/browserWallet/requestServer.ts +5 -1
  111. package/src/identity/wallet/browserWallet/requests.ts +10 -28
  112. package/src/identity/wallet/browserWallet/session.ts +26 -33
  113. package/src/identity/wallet/browserWallet/validation.ts +14 -0
  114. package/src/identity/wallet/browserWallet/walletPageSource.ts +22 -40
  115. package/src/identity/wallet/page/boot.ts +43 -0
  116. package/src/identity/wallet/page/config.ts +59 -0
  117. package/src/identity/wallet/page/constants.ts +12 -0
  118. package/src/identity/wallet/page/copy.ts +47 -68
  119. package/src/identity/wallet/page/css.ts +638 -0
  120. package/src/identity/wallet/page/{errorView.ts → errors.ts} +5 -14
  121. package/src/identity/wallet/page/{controller.ts → flow.ts} +4 -71
  122. package/src/identity/wallet/page/markup.ts +44 -34
  123. package/src/identity/wallet/page/{walletProvider.ts → provider.ts} +0 -3
  124. package/src/identity/wallet/page/resize.ts +95 -0
  125. package/src/identity/wallet/page/state.ts +135 -8
  126. package/src/identity/wallet/page/timeline.ts +161 -0
  127. package/src/identity/wallet/page/view.ts +22 -302
  128. package/src/storage/config.ts +30 -80
  129. package/src/storage/reset.ts +31 -0
  130. package/src/storage/secrets.ts +1 -16
  131. package/src/ui/Select.tsx +27 -5
  132. package/src/ui/Spinner.tsx +16 -15
  133. package/src/ui/Surface.tsx +21 -17
  134. package/src/ui/TextArea.tsx +173 -0
  135. package/src/ui/TextInput.tsx +31 -133
  136. package/src/ui/theme.ts +22 -13
  137. package/src/utils/clipboard.ts +0 -140
  138. package/src/app/FirstRun.tsx +0 -577
  139. package/src/app/FirstRunTimeline.tsx +0 -51
  140. package/src/app/firstRunConfig.ts +0 -26
  141. package/src/app/hooks/useCancelRequest.ts +0 -22
  142. package/src/app/hooks/useDoublePress.ts +0 -46
  143. package/src/app/hooks/useExitOnCtrlC.ts +0 -36
  144. package/src/auth/openaiOAuth/credentials.ts +0 -47
  145. package/src/auth/openaiOAuth/crypto.ts +0 -23
  146. package/src/auth/openaiOAuth/index.ts +0 -238
  147. package/src/auth/openaiOAuth/landingPage.ts +0 -116
  148. package/src/auth/openaiOAuth/listener.ts +0 -151
  149. package/src/auth/openaiOAuth/refresh.ts +0 -70
  150. package/src/auth/openaiOAuth/shared.ts +0 -115
  151. package/src/chat/ChatBottomPane.tsx +0 -296
  152. package/src/chat/ChatScreen.tsx +0 -1685
  153. package/src/chat/ConversationStack.tsx +0 -56
  154. package/src/chat/MessageList.tsx +0 -638
  155. package/src/chat/SessionStatus.tsx +0 -53
  156. package/src/chat/chatEnvironment.ts +0 -16
  157. package/src/chat/chatScreenUtils.ts +0 -194
  158. package/src/chat/chatSessionState.ts +0 -146
  159. package/src/chat/chatTurnContext.ts +0 -50
  160. package/src/chat/chatTurnOrchestrator.ts +0 -603
  161. package/src/chat/chatTurnRows.ts +0 -64
  162. package/src/chat/commands.ts +0 -494
  163. package/src/chat/continuityEditReview.ts +0 -42
  164. package/src/chat/display/DiffView.tsx +0 -193
  165. package/src/chat/display/SyntaxText.tsx +0 -192
  166. package/src/chat/display/toolCallDisplay.ts +0 -103
  167. package/src/chat/display/toolResultDisplay.ts +0 -19
  168. package/src/chat/input/ChatInput.tsx +0 -625
  169. package/src/chat/input/chatInputHelpers.ts +0 -62
  170. package/src/chat/input/chatInputState.ts +0 -247
  171. package/src/chat/input/chatPaste.ts +0 -49
  172. package/src/chat/input/imageRefs.ts +0 -30
  173. package/src/chat/input/inputRendering.tsx +0 -93
  174. package/src/chat/input/textCursor.ts +0 -212
  175. package/src/chat/messageMarkdown.ts +0 -220
  176. package/src/chat/messageRows.ts +0 -43
  177. package/src/chat/planImplementation.ts +0 -62
  178. package/src/chat/slashCommandHandlers.ts +0 -122
  179. package/src/chat/slashCommandViews.ts +0 -120
  180. package/src/chat/transcript/TranscriptView.tsx +0 -184
  181. package/src/chat/transcript/transcriptViewport.ts +0 -295
  182. package/src/chat/views/ContextLimitView.tsx +0 -95
  183. package/src/chat/views/ContinuityEditReviewView.tsx +0 -50
  184. package/src/chat/views/CopyPicker.tsx +0 -50
  185. package/src/chat/views/PermissionPrompt.tsx +0 -156
  186. package/src/chat/views/PermissionsView.tsx +0 -165
  187. package/src/chat/views/PlanApprovalView.tsx +0 -91
  188. package/src/chat/views/ResumeView.tsx +0 -273
  189. package/src/chat/views/RewindView.tsx +0 -412
  190. package/src/cli/preview.tsx +0 -14
  191. package/src/cli/updateNotice.ts +0 -54
  192. package/src/identity/continuity/privateEdit/apply.ts +0 -170
  193. package/src/identity/continuity/privateEdit/diff.ts +0 -6
  194. package/src/identity/continuity/privateEdit/files.ts +0 -23
  195. package/src/identity/continuity/privateEdit/types.ts +0 -28
  196. package/src/identity/continuity/privateEdit.ts +0 -46
  197. package/src/identity/hub/IdentityHub.tsx +0 -14
  198. package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +0 -104
  199. package/src/identity/hub/ens/effects.ts +0 -218
  200. package/src/identity/hub/shared/components/MenuScreen.tsx +0 -241
  201. package/src/identity/hub/shared/effects/types.ts +0 -53
  202. package/src/identity/hub/shared/reconciliation/index.ts +0 -14
  203. package/src/identity/wallet/page/grainient.ts +0 -278
  204. package/src/identity/wallet/page/html.ts +0 -28
  205. package/src/identity/wallet/page/styles/base.ts +0 -259
  206. package/src/identity/wallet/page/styles/components.ts +0 -262
  207. package/src/identity/wallet/page/styles/index.ts +0 -5
  208. package/src/identity/wallet/page/styles/responsive.ts +0 -247
  209. package/src/identity/wallet/page.tsx +0 -38
  210. package/src/mcp/approvals.ts +0 -113
  211. package/src/mcp/config.ts +0 -235
  212. package/src/mcp/manager.ts +0 -482
  213. package/src/mcp/managerHelpers.ts +0 -70
  214. package/src/mcp/names.ts +0 -19
  215. package/src/mcp/output.ts +0 -96
  216. package/src/models/ModelPicker.tsx +0 -1009
  217. package/src/models/catalog.ts +0 -327
  218. package/src/models/huggingface.ts +0 -712
  219. package/src/models/huggingfaceStorage.ts +0 -136
  220. package/src/models/llamacpp.ts +0 -848
  221. package/src/models/llamacppCommands.ts +0 -44
  222. package/src/models/llamacppConfig.ts +0 -34
  223. package/src/models/llamacppDiscovery.ts +0 -176
  224. package/src/models/llamacppOutput.ts +0 -65
  225. package/src/models/llamacppPreflight.ts +0 -158
  226. package/src/models/modelDisplay.ts +0 -180
  227. package/src/models/modelPickerCatalogFlow.ts +0 -56
  228. package/src/models/modelPickerCredentials.ts +0 -166
  229. package/src/models/modelPickerData.ts +0 -41
  230. package/src/models/modelPickerDisplay.tsx +0 -132
  231. package/src/models/modelPickerHfFlow.ts +0 -192
  232. package/src/models/modelPickerLocalRunnerFlow.ts +0 -115
  233. package/src/models/modelPickerOptions.ts +0 -457
  234. package/src/models/modelPickerTypes.ts +0 -69
  235. package/src/models/modelPickerUninstallFlow.ts +0 -48
  236. package/src/models/modelPickerViewHelpers.ts +0 -174
  237. package/src/models/modelRecommendation.ts +0 -139
  238. package/src/models/providerDisplay.ts +0 -16
  239. package/src/models/runtimeDetection.ts +0 -81
  240. package/src/models/uncensoredCatalog.ts +0 -86
  241. package/src/providers/anthropic.ts +0 -290
  242. package/src/providers/contracts.ts +0 -71
  243. package/src/providers/errors.ts +0 -80
  244. package/src/providers/gemini.ts +0 -391
  245. package/src/providers/openai-chat.ts +0 -474
  246. package/src/providers/openai-responses-format.ts +0 -177
  247. package/src/providers/openai-responses.ts +0 -306
  248. package/src/providers/openaiChatWire.ts +0 -124
  249. package/src/providers/registry.ts +0 -120
  250. package/src/providers/retry.ts +0 -58
  251. package/src/providers/sse.ts +0 -93
  252. package/src/runtime/compaction.ts +0 -395
  253. package/src/runtime/cwd.ts +0 -43
  254. package/src/runtime/providerTurn.ts +0 -38
  255. package/src/runtime/sessionMode.ts +0 -55
  256. package/src/runtime/systemPrompt.ts +0 -213
  257. package/src/runtime/textToolParser.ts +0 -161
  258. package/src/runtime/toolClaimGuards.ts +0 -143
  259. package/src/runtime/toolExecution.ts +0 -304
  260. package/src/runtime/toolIntent.ts +0 -143
  261. package/src/runtime/turn.ts +0 -369
  262. package/src/runtime/turnNudges.ts +0 -223
  263. package/src/runtime/turnTypes.ts +0 -86
  264. package/src/storage/factoryReset.ts +0 -127
  265. package/src/storage/history.ts +0 -58
  266. package/src/storage/permissions.ts +0 -76
  267. package/src/storage/rewind.ts +0 -266
  268. package/src/storage/sessionExport.ts +0 -49
  269. package/src/storage/sessions.ts +0 -495
  270. package/src/tools/bashSafety.ts +0 -186
  271. package/src/tools/bashTool.ts +0 -140
  272. package/src/tools/changeDirectoryTool.ts +0 -213
  273. package/src/tools/contracts.ts +0 -192
  274. package/src/tools/deleteFileTool.ts +0 -116
  275. package/src/tools/editTool.ts +0 -165
  276. package/src/tools/editUtils.ts +0 -170
  277. package/src/tools/fileDiff.ts +0 -261
  278. package/src/tools/listDirectoryTool.ts +0 -55
  279. package/src/tools/listSkillFilesTool.ts +0 -77
  280. package/src/tools/listSkillsTool.ts +0 -68
  281. package/src/tools/mcpResourceTools.ts +0 -95
  282. package/src/tools/permissionRules.ts +0 -85
  283. package/src/tools/privateContinuityEditTool.ts +0 -187
  284. package/src/tools/privateContinuityReadTool.ts +0 -106
  285. package/src/tools/readSkillTool.ts +0 -107
  286. package/src/tools/readTool.ts +0 -85
  287. package/src/tools/registry.ts +0 -103
  288. package/src/tools/writeFileTool.ts +0 -167
  289. package/src/ui/BrandSplash.tsx +0 -133
  290. package/src/ui/terminalTitle.ts +0 -30
  291. package/src/utils/images.ts +0 -140
  292. package/src/utils/markdownSegments.ts +0 -51
  293. package/src/utils/messages.ts +0 -37
  294. package/src/utils/withRetry.ts +0 -324
  295. /package/src/identity/{hub → manager}/continuity/state.ts +0 -0
  296. /package/src/identity/{hub → manager}/custody/helpers.ts +0 -0
  297. /package/src/identity/{hub → manager}/custody/preflight.ts +0 -0
  298. /package/src/identity/{hub → manager}/custody/state.ts +0 -0
  299. /package/src/identity/{hub → manager}/custody/useCustodyFlow.tsx +0 -0
  300. /package/src/identity/{hub → manager}/ens/EnsEditFlow.tsx +0 -0
  301. /package/src/identity/{hub → manager}/ens/advancedEnsValidation.ts +0 -0
  302. /package/src/identity/{hub → manager}/ens/state.ts +0 -0
  303. /package/src/identity/{hub → manager}/ens/transactions.ts +0 -0
  304. /package/src/identity/{hub → manager}/ens/types.ts +0 -0
  305. /package/src/identity/{hub → manager}/profile/identity.ts +0 -0
  306. /package/src/identity/{hub → manager}/restore/envelopes.ts +0 -0
  307. /package/src/identity/{hub → manager}/restore/helpers.ts +0 -0
  308. /package/src/identity/{hub → manager}/restore/recovery.ts +0 -0
  309. /package/src/identity/{hub → manager}/restore/resolve.ts +0 -0
  310. /package/src/identity/{hub → manager}/shared/components/BusyScreen.tsx +0 -0
  311. /package/src/identity/{hub → manager}/shared/components/NetworkScreen.tsx +0 -0
  312. /package/src/identity/{hub → manager}/shared/components/PinataJwtInput.tsx +0 -0
  313. /package/src/identity/{hub → manager}/shared/effects/receipts.ts +0 -0
  314. /package/src/identity/{hub → manager}/shared/effects/sync.ts +0 -0
  315. /package/src/identity/{hub → manager}/shared/model/format.ts +0 -0
  316. /package/src/identity/{hub → manager}/shared/operatorWallets.ts +0 -0
  317. /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/ownership.ts +0 -0
  318. /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/types.ts +0 -0
  319. /package/src/identity/{hub → manager}/shared/reconciliation/walletSetup.ts +0 -0
  320. /package/src/identity/{hub → manager}/shared/txGuard.ts +0 -0
  321. /package/src/identity/{hub → manager}/transfer/progress.ts +0 -0
  322. /package/src/identity/{hub → manager}/transfer/state.ts +0 -0
@@ -1,56 +0,0 @@
1
- import React from 'react'
2
- import { Box, Static } from 'ink'
3
- import { TranscriptView } from './transcript/TranscriptView.js'
4
- import type { MessageRow } from './MessageList.js'
5
-
6
- type ConversationStackProps = {
7
- header: React.ReactNode
8
- rows: MessageRow[]
9
- transcriptActive?: boolean
10
- bottomVariant?: 'prompt' | 'overlay'
11
- bottom: React.ReactNode
12
- status?: React.ReactNode
13
- sessionKey: number
14
- onVisibleReasoningIdsChange?: (ids: string[]) => void
15
- onTranscriptScrollabilityChange?: (canScroll: boolean) => void
16
- }
17
-
18
- export const ConversationStack: React.FC<ConversationStackProps> = ({
19
- header,
20
- rows,
21
- transcriptActive = true,
22
- bottomVariant = 'prompt',
23
- bottom,
24
- status,
25
- sessionKey,
26
- onVisibleReasoningIdsChange,
27
- onTranscriptScrollabilityChange,
28
- }) => {
29
- return (
30
- <>
31
- {header ? (
32
- <Static items={[{ id: `header-${sessionKey}`, node: header }]}>
33
- {item => <Box key={item.id} paddingX={1} paddingTop={1}>{item.node}</Box>}
34
- </Static>
35
- ) : null}
36
- <Box flexDirection="column" padding={1}>
37
- <TranscriptView
38
- key={`transcript-${sessionKey}`}
39
- rows={rows}
40
- active={transcriptActive}
41
- bottomVariant={bottomVariant}
42
- onVisibleReasoningIdsChange={onVisibleReasoningIdsChange}
43
- onScrollabilityChange={onTranscriptScrollabilityChange}
44
- />
45
- <Box marginTop={1} width="100%">
46
- {bottom}
47
- </Box>
48
- {status ? (
49
- <Box marginTop={1}>
50
- {status}
51
- </Box>
52
- ) : null}
53
- </Box>
54
- </>
55
- )
56
- }
@@ -1,638 +0,0 @@
1
- import React, { useEffect, useMemo, useState } from 'react'
2
- import { Box, Text } from 'ink'
3
- import { theme } from '../ui/theme.js'
4
- import { ProgressBar } from '../ui/ProgressBar.js'
5
- import { Spinner } from '../ui/Spinner.js'
6
- import { DiffView } from './display/DiffView.js'
7
- import { SyntaxLine } from './display/SyntaxText.js'
8
- import { formatToolCall } from './display/toolCallDisplay.js'
9
- import { BrandSplash } from '../ui/BrandSplash.js'
10
- import type { RowSlice } from './transcript/transcriptViewport.js'
11
- import {
12
- blockContentWidth,
13
- clipTextForDisplay,
14
- parseInlineTokens,
15
- parseMarkdownBlocks,
16
- sanitizeReasoningForDisplay,
17
- summarizeThinking,
18
- type InlineToken,
19
- type MarkdownBlock,
20
- } from './messageMarkdown.js'
21
-
22
- export { sanitizeReasoningForDisplay } from './messageMarkdown.js'
23
-
24
- export type ToolCallResult = {
25
- content: string
26
- summary: string
27
- isError: boolean
28
- diff?: string
29
- }
30
-
31
- export type MessageRow =
32
- | { role: 'user'; id: string; content: string }
33
- | { role: 'assistant'; id: string; content: string; liveTail?: string; streaming?: boolean }
34
- | { role: 'thinking'; id: string; content: string; liveTail?: string; streaming?: boolean; expanded?: boolean; showCursor?: boolean }
35
- | {
36
- role: 'tool_call'
37
- id: string
38
- name: string
39
- summary: string
40
- input?: Record<string, unknown>
41
- result?: ToolCallResult
42
- }
43
- | { role: 'note'; id: string; kind: 'info' | 'error' | 'dim'; content: string }
44
- | {
45
- role: 'progress'
46
- id: string
47
- title: string
48
- progress: number
49
- status: string
50
- suffix?: string
51
- done?: boolean
52
- indeterminate?: boolean
53
- startedAt?: number
54
- }
55
- | {
56
- role: 'splash'
57
- id: string
58
- contextLine?: string
59
- tipLine?: string
60
- updateNotice?: string | null
61
- }
62
-
63
- type MessageListProps = {
64
- slices: Array<RowSlice<MessageRow>>
65
- }
66
-
67
- export {
68
- rowsToFullSlices,
69
- toggleInspectableRow,
70
- toggleLatestReasoningRow,
71
- toggleReasoningRow,
72
- } from './messageRows.js'
73
-
74
- const MAX_RENDERED_MESSAGE_CHARS = 12_000
75
- const MAX_RENDERED_REASONING_CHARS = 10_000
76
- const ASSISTANT_ACCENT = theme.accentPeriwinkle
77
- const ASSISTANT_MARKER = '• '
78
-
79
- const MessageListInner: React.FC<MessageListProps> = ({ slices }) => (
80
- <Box flexDirection="column">
81
- {slices.map((slice, index) => (
82
- <RowView
83
- key={slice.row.id}
84
- slice={slice}
85
- tightTop={slice.row.role === 'tool_call' && slices[index - 1]?.row.role === 'tool_call'}
86
- />
87
- ))}
88
- </Box>
89
- )
90
-
91
- export const MessageList = React.memo(MessageListInner)
92
-
93
- const RowViewInner: React.FC<{ slice: RowSlice<MessageRow>; tightTop?: boolean }> = ({ slice, tightTop }) => {
94
- const { row, clipStart, clipEnd, rowHeight } = slice
95
- if (row.role === 'user') {
96
- const display = clipTextForDisplay(row.content, MAX_RENDERED_MESSAGE_CHARS)
97
- const lines = display.text.length === 0 ? [''] : display.text.split('\n')
98
- return (
99
- <Box flexDirection="column" marginTop={1}>
100
- {display.omittedChars > 0 ? (
101
- <Text color={theme.dim}>{` ${display.omittedChars} earlier characters omitted`}</Text>
102
- ) : null}
103
- {lines.map((line, i) => (
104
- <Text key={i}>
105
- <Text color={i === 0 ? theme.accentPeriwinkle : theme.dim}>{i === 0 ? '> ' : ' '}</Text>
106
- <Text color={theme.textSubtle}>{line}</Text>
107
- </Text>
108
- ))}
109
- </Box>
110
- )
111
- }
112
-
113
- if (row.role === 'assistant') {
114
- const showTopMargin = clipStart === 0
115
- const bodyClipStart = showTopMargin ? 0 : clipStart - 1
116
- const bodyClipEnd = clipEnd !== undefined ? Math.max(0, clipEnd - 1) : undefined
117
- return (
118
- <Box flexDirection="column" marginTop={showTopMargin ? 1 : 0}>
119
- <AssistantBody
120
- content={row.content}
121
- liveTail={row.liveTail}
122
- streaming={row.streaming}
123
- clipStart={bodyClipStart}
124
- clipEnd={bodyClipEnd}
125
- />
126
- </Box>
127
- )
128
- }
129
-
130
- if (row.role === 'thinking') {
131
- const text = sanitizeReasoningForDisplay(reasoningText(row))
132
- const preview = summarizeThinking(text)
133
- const active = Boolean(row.streaming)
134
- const showCursor = reasoningCursorVisible(row)
135
- if (row.expanded) {
136
- return (
137
- <ReasoningBlock
138
- content={text}
139
- detail="alt+t collapse"
140
- expanded
141
- active={active}
142
- showCursor={showCursor}
143
- clipStart={clipStart}
144
- clipEnd={clipEnd}
145
- rowHeight={rowHeight}
146
- />
147
- )
148
- }
149
- return (
150
- <ReasoningBlock
151
- content={preview || 'thinking...'}
152
- detail="alt+t inspect"
153
- active={active}
154
- showCursor={showCursor}
155
- clipStart={clipStart}
156
- clipEnd={clipEnd}
157
- rowHeight={rowHeight}
158
- />
159
- )
160
- }
161
-
162
- if (row.role === 'tool_call') {
163
- const { displayName, argSummary } = formatToolCall(row.name, row.input)
164
- const result = row.result
165
- const showResultLine = !result || result.isError
166
- return (
167
- <Box flexDirection="column" marginTop={tightTop ? 0 : 1}>
168
- <Text>
169
- <Text color={theme.dim}>{'● '}</Text>
170
- <Text color={theme.accentPeriwinkle} bold>{displayName}</Text>
171
- {row.name !== 'run_bash' && argSummary ? <Text color={theme.textSubtle}>{` ${argSummary}`}</Text> : null}
172
- {result && !result.isError ? <Text color={theme.dim}>{' done'}</Text> : null}
173
- </Text>
174
- {row.name === 'run_bash' && argSummary ? (
175
- <Box marginLeft={2}>
176
- <Text>
177
- <Text color={theme.dim}>{'$ '}</Text>
178
- <Text color={theme.text}>{argSummary}</Text>
179
- </Text>
180
- </Box>
181
- ) : null}
182
- {showResultLine ? (
183
- result ? (
184
- <Box marginLeft={2}>
185
- <Text color={result.isError ? theme.accentError : theme.dim}>{result.summary}</Text>
186
- </Box>
187
- ) : (
188
- <Box marginLeft={2}>
189
- <Text color={theme.dim}>running…</Text>
190
- </Box>
191
- )
192
- ) : null}
193
- {result?.diff && !result.isError ? (
194
- <Box flexDirection="column" marginLeft={2}>
195
- <DiffView diff={result.diff} />
196
- </Box>
197
- ) : null}
198
- </Box>
199
- )
200
- }
201
-
202
- if (row.role === 'note') {
203
- const color = row.kind === 'error' ? theme.accentError : row.kind === 'dim' ? theme.dim : theme.accentPeriwinkle
204
- return (
205
- <Box marginTop={1}>
206
- <Text color={color}>{row.content}</Text>
207
- </Box>
208
- )
209
- }
210
-
211
- if (row.role === 'splash') {
212
- return (
213
- <BrandSplash
214
- contextLine={row.contextLine}
215
- tipLine={row.tipLine}
216
- updateNotice={row.updateNotice ?? null}
217
- />
218
- )
219
- }
220
-
221
- return (
222
- <Box flexDirection="column" marginTop={1}>
223
- <Text color={theme.accentPeriwinkle} bold>{row.title}</Text>
224
- {row.indeterminate ? (
225
- <ProgressSpinner row={row} />
226
- ) : (
227
- <>
228
- <Text color={theme.dim}>{row.status}</Text>
229
- <ProgressBar progress={row.progress} suffix={row.suffix} />
230
- </>
231
- )}
232
- </Box>
233
- )
234
- }
235
-
236
- const RowView = React.memo(RowViewInner)
237
-
238
- const ProgressSpinner: React.FC<{ row: Extract<MessageRow, { role: 'progress' }> }> = ({ row }) => {
239
- return <Spinner active label={row.status} hint={row.suffix} startedAt={row.startedAt} />
240
- }
241
-
242
- const ShimmerText: React.FC<{
243
- text: string
244
- color: string
245
- active?: boolean
246
- bold?: boolean
247
- italic?: boolean
248
- }> = ({ text, color, active = false, bold, italic }) => {
249
- const [position, setPosition] = useState(0)
250
-
251
- useEffect(() => {
252
- if (!active) return
253
- const period = text.length + 8
254
- const timer = setInterval(() => {
255
- setPosition(prev => (prev + 1) % period)
256
- }, 90)
257
- return () => clearInterval(timer)
258
- }, [active, text.length])
259
-
260
- if (!active) {
261
- return <Text color={color} bold={bold} italic={italic}>{text}</Text>
262
- }
263
-
264
- const shimmerStart = position - 1
265
- const shimmerEnd = position + 1
266
- const visibleStart = Math.max(0, shimmerStart)
267
- const visibleEnd = Math.min(text.length, shimmerEnd + 1)
268
- const before = text.slice(0, visibleStart)
269
- const shimmer = shimmerStart < text.length && shimmerEnd >= 0 ? text.slice(visibleStart, visibleEnd) : ''
270
- const after = text.slice(visibleEnd)
271
-
272
- return (
273
- <>
274
- {before ? <Text color={color} bold={bold} italic={italic}>{before}</Text> : null}
275
- {shimmer ? <Text color={theme.accentWhite} bold italic={italic}>{shimmer}</Text> : null}
276
- {after ? <Text color={color} bold={bold} italic={italic}>{after}</Text> : null}
277
- </>
278
- )
279
- }
280
-
281
- export function reasoningBorderColor(row: Extract<MessageRow, { role: 'thinking' }>): string {
282
- return row.streaming ? theme.accentPeriwinkle : theme.border
283
- }
284
-
285
- export function reasoningCursorVisible(row: Extract<MessageRow, { role: 'thinking' }>): boolean {
286
- return Boolean(row.streaming && row.showCursor)
287
- }
288
-
289
- const ReasoningBlock: React.FC<{
290
- content: string
291
- detail: string
292
- active?: boolean
293
- expanded?: boolean
294
- showCursor?: boolean
295
- clipStart?: number
296
- clipEnd?: number
297
- rowHeight?: number
298
- }> = ({ content, detail, active = false, expanded = false, showCursor, clipStart = 0, clipEnd, rowHeight }) => {
299
- const display = useMemo(
300
- () => clipTextForDisplay(content, MAX_RENDERED_REASONING_CHARS),
301
- [content],
302
- )
303
- const lines = useMemo(() => {
304
- const normalized = display.text.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
305
- return normalized.length === 0 ? [''] : normalized.split('\n')
306
- }, [display.text])
307
-
308
- const omittedVisible = display.omittedChars > 0
309
- const totalLines = rowHeight ?? (1 + (omittedVisible ? 1 : 0) + 1 + (expanded ? lines.length : 0))
310
- const effClipEnd = clipEnd ?? totalLines
311
-
312
- const lineInClip = (n: number) => n >= clipStart && n < effClipEnd
313
- const showMargin = lineInClip(0)
314
- const omittedLine = 1
315
- const headerLine = omittedVisible ? 2 : 1
316
- const bodyStartLine = headerLine + 1
317
-
318
- const lastBodyIndex = lines.length - 1
319
- const visibleBody: Array<{ index: number; text: string }> = []
320
- if (expanded) {
321
- for (let i = 0; i < lines.length; i += 1) {
322
- const ln = bodyStartLine + i
323
- if (ln >= effClipEnd) break
324
- if (ln < clipStart) continue
325
- visibleBody.push({ index: i, text: lines[i] ?? '' })
326
- }
327
- }
328
-
329
- return (
330
- <Box flexDirection="column" marginTop={showMargin ? 1 : 0}>
331
- {omittedVisible && lineInClip(omittedLine) ? (
332
- <Text color={theme.dim}>{`${display.omittedChars} earlier reasoning characters omitted`}</Text>
333
- ) : null}
334
- {lineInClip(headerLine) ? (
335
- <Text>
336
- <AssistantMarker />
337
- <ShimmerText
338
- text={expanded ? 'Thinking…' : 'Thinking'}
339
- color={theme.accentPeriwinkle}
340
- active={active}
341
- bold
342
- italic
343
- />
344
- <Text color={theme.dim}>{` · ${detail}`}</Text>
345
- {!expanded && showCursor ? <ThinkingCursor active hasPreview /> : null}
346
- </Text>
347
- ) : null}
348
- {expanded && visibleBody.length > 0 ? (
349
- <Box flexDirection="column" marginLeft={2}>
350
- {visibleBody.map(({ index, text }) => (
351
- <Box key={index}>
352
- <Text color={theme.textSubtle}>
353
- <Text color={theme.dim}>{' '}</Text>
354
- {text || ' '}
355
- {showCursor && index === lastBodyIndex ? <ThinkingCursor active hasPreview={text.length > 0} /> : null}
356
- </Text>
357
- </Box>
358
- ))}
359
- </Box>
360
- ) : null}
361
- </Box>
362
- )
363
- }
364
-
365
- const AssistantBody: React.FC<{
366
- content: string
367
- liveTail?: string
368
- streaming?: boolean
369
- clipStart?: number
370
- clipEnd?: number
371
- }> = ({ content, liveTail, streaming, clipStart = 0, clipEnd }) => {
372
- const fullText = liveTail ? content + liveTail : content
373
- const nodes = useMemo(
374
- () => flattenAssistantBody(fullText, Boolean(streaming)),
375
- [fullText, streaming],
376
- )
377
- const effClipEnd = clipEnd ?? nodes.length
378
- const visible = nodes.slice(clipStart, effClipEnd)
379
- return (
380
- <Box flexDirection="column">
381
- {visible.map((node, i) => (
382
- <React.Fragment key={clipStart + i}>{node}</React.Fragment>
383
- ))}
384
- </Box>
385
- )
386
- }
387
-
388
- export function flattenAssistantBody(fullText: string, streaming: boolean): React.ReactNode[] {
389
- const display = clipTextForDisplay(fullText, MAX_RENDERED_MESSAGE_CHARS)
390
- const blocks = parseMarkdownBlocks(display.text)
391
- const nodes: React.ReactNode[] = []
392
-
393
- if (display.omittedChars > 0) {
394
- nodes.push(
395
- <Text color={theme.dim}>{`${display.omittedChars} earlier characters omitted`}</Text>,
396
- )
397
- }
398
-
399
- if (blocks.length === 0) {
400
- if (streaming) {
401
- nodes.push(
402
- <Text>
403
- <AssistantMarker />
404
- <Text color={ASSISTANT_ACCENT}><StreamCursor active /></Text>
405
- </Text>,
406
- )
407
- }
408
- return nodes
409
- }
410
-
411
- for (let bi = 0; bi < blocks.length; bi += 1) {
412
- const block = blocks[bi]!
413
- const isLastBlock = bi === blocks.length - 1
414
- const prefix = bi === 0 || block.kind === 'code' ? <AssistantMarker /> : null
415
- const streamingLast = streaming && isLastBlock
416
-
417
- nodes.push(<Text> </Text>)
418
-
419
- if (block.kind === 'heading') {
420
- nodes.push(
421
- <Text>
422
- {prefix}
423
- <InlineText text={block.text} color={ASSISTANT_ACCENT} bold />
424
- </Text>,
425
- )
426
- continue
427
- }
428
-
429
- if (block.kind === 'quote') {
430
- block.lines.forEach((line, li) => {
431
- nodes.push(
432
- <Text>
433
- {li === 0 ? prefix : null}
434
- <Text color={ASSISTANT_ACCENT}>| </Text>
435
- <InlineText text={line} color={theme.dim} />
436
- </Text>,
437
- )
438
- })
439
- continue
440
- }
441
-
442
- if (block.kind === 'list') {
443
- block.items.forEach((item, li) => {
444
- nodes.push(
445
- <Text>
446
- {li === 0 ? prefix : null}
447
- <Text color={ASSISTANT_ACCENT}>{block.ordered ? `${li + 1}. ` : '- '}</Text>
448
- <InlineText text={item} color={theme.text} />
449
- </Text>,
450
- )
451
- })
452
- continue
453
- }
454
-
455
- if (block.kind === 'code') {
456
- nodes.push(
457
- <Text>
458
- {prefix}
459
- <Text color={theme.accentPeriwinkle} bold>{block.lang ?? 'code'}</Text>
460
- {block.open ? <Text color={theme.dim}> streaming</Text> : null}
461
- </Text>,
462
- )
463
- const codeLines = block.code.length === 0 ? [''] : block.code.split('\n')
464
- const isShell = block.lang === 'bash' || block.lang === 'sh'
465
- codeLines.forEach((line, li) => {
466
- const isLastCode = li === codeLines.length - 1
467
- nodes.push(
468
- <Text>
469
- <Text color={theme.dim}>{` ${isShell ? '$ ' : ' '}`}</Text>
470
- <SyntaxLine line={line} lang={block.lang} fallbackColor={theme.textSubtle} />
471
- {block.open && isLastCode ? <Text color={ASSISTANT_ACCENT}> <StreamCursor active /></Text> : null}
472
- </Text>,
473
- )
474
- })
475
- continue
476
- }
477
-
478
- const paragraphLines = block.text.split('\n')
479
- paragraphLines.forEach((line, li) => {
480
- const isLastLine = li === paragraphLines.length - 1
481
- nodes.push(
482
- <Text>
483
- {li === 0 ? prefix : null}
484
- <InlineText text={line} color={theme.text} />
485
- {streamingLast && isLastLine ? <Text color={ASSISTANT_ACCENT}> <StreamCursor active /></Text> : null}
486
- </Text>,
487
- )
488
- })
489
- }
490
-
491
- return nodes
492
- }
493
-
494
- const AssistantMarker: React.FC = () => (
495
- <Text color={theme.dim}>{ASSISTANT_MARKER}</Text>
496
- )
497
-
498
- const MarkdownBlockView: React.FC<{ block: MarkdownBlock; streaming?: boolean; prefix?: React.ReactNode }> = ({
499
- block,
500
- streaming = false,
501
- prefix = null,
502
- }) => {
503
- if (block.kind === 'heading') {
504
- return (
505
- <Box flexDirection="column" marginTop={1}>
506
- <Text>
507
- {prefix}
508
- <InlineText text={block.text} color={ASSISTANT_ACCENT} bold />
509
- </Text>
510
- </Box>
511
- )
512
- }
513
-
514
- if (block.kind === 'quote') {
515
- return (
516
- <Box flexDirection="column" marginTop={1}>
517
- {block.lines.map((line, index) => (
518
- <Text key={index}>
519
- {index === 0 ? prefix : null}
520
- <Text color={ASSISTANT_ACCENT}>| </Text>
521
- <InlineText text={line} color={theme.dim} />
522
- </Text>
523
- ))}
524
- </Box>
525
- )
526
- }
527
-
528
- if (block.kind === 'list') {
529
- return (
530
- <Box flexDirection="column" marginTop={1}>
531
- {block.items.map((item, index) => (
532
- <Text key={index}>
533
- {index === 0 ? prefix : null}
534
- <Text color={ASSISTANT_ACCENT}>{block.ordered ? `${index + 1}. ` : '- '}</Text>
535
- <InlineText text={item} color={theme.text} />
536
- </Text>
537
- ))}
538
- </Box>
539
- )
540
- }
541
-
542
- if (block.kind === 'code') {
543
- const lines = block.code.length === 0 ? [''] : block.code.split('\n')
544
- const isShell = block.lang === 'bash' || block.lang === 'sh'
545
- return (
546
- <Box flexDirection="column" marginTop={1}>
547
- <Text>
548
- {prefix}
549
- <Text color={theme.accentPeriwinkle} bold>{block.lang ?? 'code'}</Text>
550
- {block.open ? <Text color={theme.dim}> streaming</Text> : null}
551
- </Text>
552
- <Box flexDirection="column" marginLeft={2}>
553
- {lines.map((line, index) => (
554
- <Text key={index}>
555
- <Text color={theme.dim}>{isShell ? '$ ' : ' '}</Text>
556
- <SyntaxLine line={line} lang={block.lang} fallbackColor={theme.textSubtle} />
557
- {block.open && index === lines.length - 1 ? <Text color={ASSISTANT_ACCENT}> <StreamCursor active /></Text> : null}
558
- </Text>
559
- ))}
560
- </Box>
561
- </Box>
562
- )
563
- }
564
-
565
- return (
566
- <Box flexDirection="column" marginTop={1}>
567
- <Text>
568
- {prefix}
569
- <InlineText text={block.text} color={theme.text} />
570
- {streaming ? <Text color={ASSISTANT_ACCENT}> <StreamCursor active /></Text> : null}
571
- </Text>
572
- </Box>
573
- )
574
- }
575
-
576
- const InlineText: React.FC<{ text: string; color: string; bold?: boolean }> = ({ text, color, bold }) => {
577
- const tokens = useMemo(() => parseInlineTokens(text), [text])
578
- return (
579
- <>
580
- {tokens.map((token, index) => {
581
- if (token.kind === 'bold') {
582
- return (
583
- <Text key={index} color={ASSISTANT_ACCENT} bold>
584
- {token.text}
585
- </Text>
586
- )
587
- }
588
- if (token.kind === 'italic') {
589
- return (
590
- <Text key={index} color={ASSISTANT_ACCENT} italic>
591
- {token.text}
592
- </Text>
593
- )
594
- }
595
- if (token.kind === 'code') {
596
- return (
597
- <Text key={index} color={ASSISTANT_ACCENT} backgroundColor="#202020">
598
- {token.text}
599
- </Text>
600
- )
601
- }
602
- return (
603
- <Text key={index} color={color} bold={bold}>
604
- {token.text}
605
- </Text>
606
- )
607
- })}
608
- </>
609
- )
610
- }
611
-
612
- const ThinkingCursor: React.FC<{ active: boolean; hasPreview: boolean }> = ({ active, hasPreview }) => {
613
- if (!active) return null
614
- return (
615
- <Text color={theme.accentPeriwinkle}>
616
- {hasPreview ? ' ' : ''}
617
- <StreamCursor active />
618
- </Text>
619
- )
620
- }
621
-
622
- const StreamCursor: React.FC<{ active: boolean }> = ({ active }) => {
623
- const [visible, setVisible] = useState(true)
624
-
625
- useEffect(() => {
626
- if (!active) return
627
- const timer = setInterval(() => {
628
- setVisible(v => !v)
629
- }, 420)
630
- return () => clearInterval(timer)
631
- }, [active])
632
-
633
- return <>{visible ? '|' : ' '}</>
634
- }
635
-
636
- function reasoningText(row: Extract<MessageRow, { role: 'thinking' }>): string {
637
- return row.liveTail ? row.content + row.liveTail : row.content
638
- }