ethagent 3.3.4 → 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 -260
  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,220 +0,0 @@
1
- export type MarkdownBlock =
2
- | { kind: 'heading'; level: 1 | 2 | 3 | 4 | 5 | 6; text: string }
3
- | { kind: 'paragraph'; text: string }
4
- | { kind: 'quote'; lines: string[] }
5
- | { kind: 'list'; ordered: boolean; items: string[] }
6
- | { kind: 'code'; lang: string | null; code: string; open?: boolean }
7
-
8
- export type InlineToken =
9
- | { kind: 'text'; text: string }
10
- | { kind: 'bold'; text: string }
11
- | { kind: 'italic'; text: string }
12
- | { kind: 'code'; text: string }
13
-
14
- const UNREADABLE_REASONING_TEXT = 'reasoning output was not readable text'
15
-
16
- export function blockContentWidth(lines: string[]): number {
17
- return Math.max(1, ...lines.map(displayWidth))
18
- }
19
-
20
- function displayWidth(line: string): number {
21
- return (line || ' ').replace(/\t/g, ' ').length
22
- }
23
-
24
- export function parseMarkdownBlocks(markdown: string): MarkdownBlock[] {
25
- const text = markdown.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
26
- if (!text.trim()) return []
27
-
28
- const blocks: MarkdownBlock[] = []
29
- const lines = text.split('\n')
30
- let index = 0
31
-
32
- while (index < lines.length) {
33
- const line = lines[index] ?? ''
34
- const trimmed = line.trim()
35
-
36
- if (!trimmed) {
37
- index += 1
38
- continue
39
- }
40
-
41
- const fence = trimmed.match(/^```([\w+-]*)\s*$/)
42
- if (fence) {
43
- const lang = fence[1] && fence[1].length > 0 ? fence[1] : null
44
- index += 1
45
- const body: string[] = []
46
- let closed = false
47
- while (index < lines.length) {
48
- const nextLine = lines[index] ?? ''
49
- if (nextLine.trim().match(/^```\s*$/)) {
50
- closed = true
51
- index += 1
52
- break
53
- }
54
- body.push(nextLine)
55
- index += 1
56
- }
57
- blocks.push({ kind: 'code', lang, code: body.join('\n'), open: !closed })
58
- continue
59
- }
60
-
61
- const heading = line.match(/^(#{1,6})\s+(.*)$/)
62
- if (heading) {
63
- const [, hashes = '#', headingText = ''] = heading
64
- blocks.push({
65
- kind: 'heading',
66
- level: hashes.length as 1 | 2 | 3 | 4 | 5 | 6,
67
- text: headingText.trim(),
68
- })
69
- index += 1
70
- continue
71
- }
72
-
73
- if (/^>\s?/.test(trimmed)) {
74
- const quoteLines: string[] = []
75
- while (index < lines.length) {
76
- const nextLine = lines[index] ?? ''
77
- if (!/^>\s?/.test(nextLine.trim())) break
78
- quoteLines.push(nextLine.trim().replace(/^>\s?/, ''))
79
- index += 1
80
- }
81
- blocks.push({ kind: 'quote', lines: quoteLines })
82
- continue
83
- }
84
-
85
- const ordered = trimmed.match(/^\d+\.\s+(.*)$/)
86
- const unordered = trimmed.match(/^[-*+]\s+(.*)$/)
87
- if (ordered || unordered) {
88
- const items: string[] = []
89
- const orderedList = Boolean(ordered)
90
- while (index < lines.length) {
91
- const nextLine = lines[index] ?? ''
92
- const match = orderedList
93
- ? nextLine.trim().match(/^\d+\.\s+(.*)$/)
94
- : nextLine.trim().match(/^[-*+]\s+(.*)$/)
95
- if (!match) break
96
- items.push(match[1] ?? '')
97
- index += 1
98
- }
99
- blocks.push({ kind: 'list', ordered: orderedList, items })
100
- continue
101
- }
102
-
103
- const paragraph: string[] = []
104
- while (index < lines.length) {
105
- const nextLine = lines[index] ?? ''
106
- const nextTrimmed = nextLine.trim()
107
- if (!nextTrimmed) break
108
- if (nextTrimmed.match(/^```([\w+-]*)\s*$/)) break
109
- if (nextLine.match(/^(#{1,6})\s+(.*)$/)) break
110
- if (/^>\s?/.test(nextTrimmed)) break
111
- if (nextTrimmed.match(/^\d+\.\s+(.*)$/) || nextTrimmed.match(/^[-*+]\s+(.*)$/)) break
112
- paragraph.push(nextLine)
113
- index += 1
114
- }
115
- blocks.push({ kind: 'paragraph', text: paragraph.join('\n').trim() })
116
- }
117
-
118
- return blocks
119
- }
120
-
121
- export function parseInlineTokens(text: string): InlineToken[] {
122
- const tokens: InlineToken[] = []
123
- const source = normalizeInlineDisplayText(text)
124
- const pattern = /(`[^`\n]+`|\*\*[^*\n]+?\*\*|__[^_\n]+?__|\*[^*\n]+?\*|_[^_\n]+?_)/g
125
- let lastIndex = 0
126
- let match: RegExpExecArray | null
127
-
128
- while ((match = pattern.exec(source)) !== null) {
129
- if (match.index > lastIndex) {
130
- tokens.push({ kind: 'text', text: cleanPlainInlineText(source.slice(lastIndex, match.index)) })
131
- }
132
-
133
- const token = match[0]
134
- if ((token.startsWith('**') && token.endsWith('**')) || (token.startsWith('__') && token.endsWith('__'))) {
135
- tokens.push({ kind: 'bold', text: cleanPlainInlineText(token.slice(2, -2)) })
136
- } else if ((token.startsWith('*') && token.endsWith('*')) || (token.startsWith('_') && token.endsWith('_'))) {
137
- tokens.push({ kind: 'italic', text: cleanPlainInlineText(token.slice(1, -1)) })
138
- } else if (token.startsWith('`') && token.endsWith('`')) {
139
- tokens.push({ kind: 'code', text: token.slice(1, -1) })
140
- }
141
-
142
- lastIndex = match.index + token.length
143
- }
144
-
145
- if (lastIndex < source.length || tokens.length === 0) {
146
- tokens.push({ kind: 'text', text: cleanPlainInlineText(source.slice(lastIndex)) })
147
- }
148
-
149
- return tokens.filter(token => token.text.length > 0)
150
- }
151
-
152
- function normalizeInlineDisplayText(text: string): string {
153
- return text
154
- .replace(/\\\(/g, '')
155
- .replace(/\\\)/g, '')
156
- .replace(/\\\[/g, '')
157
- .replace(/\\\]/g, '')
158
- .replace(/\$\$([^$]+)\$\$/g, '$1')
159
- .replace(/\$([^$\n]+)\$/g, '$1')
160
- .replace(/\\([{}[\]()])/g, '$1')
161
- .replace(/\/([{}])/g, '$1')
162
- }
163
-
164
- function cleanPlainInlineText(text: string): string {
165
- return text.replace(/\*+/g, '')
166
- }
167
-
168
- export function summarizeThinking(text: string): string {
169
- const sample = text.length > 1000 ? text.slice(-1000) : text
170
- const normalized = sample.replace(/\s+/g, ' ').trim()
171
- if (!normalized) return ''
172
- const prefix = text.length > sample.length ? '...' : ''
173
- if (normalized.length + prefix.length <= 120) return `${prefix}${normalized}`
174
- return `${prefix}${normalized.slice(Math.max(0, normalized.length - (120 - prefix.length)))}`
175
- }
176
-
177
- export function clipTextForDisplay(text: string, maxChars: number): { text: string; omittedChars: number } {
178
- if (text.length <= maxChars) return { text, omittedChars: 0 }
179
- const rawStart = Math.max(0, text.length - maxChars)
180
- const newline = text.indexOf('\n', rawStart)
181
- const start = newline >= 0 && newline - rawStart <= 240 ? newline + 1 : rawStart
182
- return {
183
- text: text.slice(start),
184
- omittedChars: start,
185
- }
186
- }
187
-
188
- export function sanitizeReasoningForDisplay(text: string): string {
189
- const normalized = text
190
- .replace(/\r\n/g, '\n')
191
- .replace(/\r/g, '\n')
192
- .replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '')
193
- const controlCount = countMatches(normalized, /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F\uFFFD]/g)
194
- const cleaned = normalized
195
- .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F\uFFFD]/g, '')
196
- .replace(/\t/g, ' ')
197
- const visibleLength = cleaned.replace(/\s/g, '').length
198
- if (visibleLength === 0) return ''
199
- if (controlCount > 0 && controlCount / Math.max(1, text.length) > 0.05) return UNREADABLE_REASONING_TEXT
200
- if (looksLikeUnreadableReasoning(cleaned)) return UNREADABLE_REASONING_TEXT
201
- return cleaned
202
- }
203
-
204
- function looksLikeUnreadableReasoning(text: string): boolean {
205
- const visible = text.replace(/\s/g, '')
206
- if (visible.length < 120) return false
207
- const letters = countMatches(visible, /[A-Za-z]/g)
208
- const digits = countMatches(visible, /\d/g)
209
- const words = text.match(/[A-Za-z]{3,}/g) ?? []
210
- const wordChars = words.reduce((sum, word) => sum + word.length, 0)
211
- const whitespace = countMatches(text, /\s/g)
212
- const symbolDensity = (visible.length - letters - digits) / visible.length
213
- const wordDensity = wordChars / visible.length
214
- const whitespaceDensity = whitespace / Math.max(1, text.length)
215
- return symbolDensity > 0.38 && wordDensity < 0.32 && whitespaceDensity < 0.12
216
- }
217
-
218
- function countMatches(text: string, pattern: RegExp): number {
219
- return text.match(pattern)?.length ?? 0
220
- }
@@ -1,43 +0,0 @@
1
- import type { RowSlice } from './transcript/transcriptViewport.js'
2
- import type { MessageRow } from './MessageList.js'
3
-
4
- export function rowsToFullSlices(rows: MessageRow[]): Array<RowSlice<MessageRow>> {
5
- return rows.map(row => ({ row, clipStart: 0, clipEnd: Number.MAX_SAFE_INTEGER, rowHeight: Number.MAX_SAFE_INTEGER }))
6
- }
7
-
8
- export function toggleLatestReasoningRow(rows: MessageRow[]): MessageRow[] {
9
- return toggleInspectableRow(rows)
10
- }
11
-
12
- export function toggleReasoningRow(rows: MessageRow[], rowId?: string): MessageRow[] {
13
- return toggleInspectableRow(rows, rowId)
14
- }
15
-
16
- export function toggleInspectableRow(rows: MessageRow[], rowId?: string): MessageRow[] {
17
- let index = -1
18
- if (rowId) {
19
- index = rows.findIndex(row => row.id === rowId && isInspectableRole(row.role))
20
- }
21
- if (index === -1) {
22
- for (let cursor = rows.length - 1; cursor >= 0; cursor -= 1) {
23
- const role = rows[cursor]?.role
24
- if (role && isInspectableRole(role)) {
25
- index = cursor
26
- break
27
- }
28
- }
29
- }
30
- if (index === -1) return rows
31
- const row = rows[index]
32
- if (!row) return rows
33
- if (row.role === 'thinking') {
34
- const next = rows.slice()
35
- next[index] = { ...row, expanded: !row.expanded }
36
- return next
37
- }
38
- return rows
39
- }
40
-
41
- function isInspectableRole(role: MessageRow['role']): boolean {
42
- return role === 'thinking'
43
- }
@@ -1,62 +0,0 @@
1
- import type { SessionMessage } from '../storage/sessions.js'
2
-
3
- const MAX_HANDOFF_SUMMARY_CHARS = 12_000
4
-
5
- export function chatFooterShortcutText(_canScrollTranscript: boolean): string {
6
- return 'alt+p model · alt+i identity'
7
- }
8
-
9
- export function buildPlanImplementationPrompt(plan: string): string {
10
- return [
11
- 'Implement the approved plan below.',
12
- '',
13
- 'Use native ethagent tools directly. Do not translate tool names into shell commands.',
14
- 'For workspace inspection, call list_directory and read_file directly.',
15
- 'For file creation or edits, call edit_file directly.',
16
- 'Use run_bash only for an actual shell command that cannot be performed by a narrower native tool, such as starting a local server after files exist.',
17
- 'Ignore any plan wording that says to execute file work as a Bash script or directly in the terminal; the native tools above are authoritative.',
18
- 'Read the relevant files before editing, make the required changes, and verify the result when possible.',
19
- '',
20
- plan,
21
- ].join('\n')
22
- }
23
-
24
- export function buildPlanTransferSeedMessages(args: {
25
- sourceSessionId: string
26
- summary: string
27
- plan: string
28
- createdAt: string
29
- }): SessionMessage[] {
30
- return [
31
- {
32
- role: 'user',
33
- synthetic: true,
34
- content: [
35
- `Planning handoff from ${args.sourceSessionId.slice(0, 8)}:`,
36
- '',
37
- args.summary.trim(),
38
- ].join('\n'),
39
- createdAt: args.createdAt,
40
- },
41
- {
42
- role: 'user',
43
- synthetic: true,
44
- content: [
45
- 'Approved plan to implement:',
46
- '',
47
- args.plan.trim(),
48
- ].join('\n'),
49
- createdAt: args.createdAt,
50
- },
51
- ]
52
- }
53
-
54
- export function normalizeHandoffSummary(summary: string): string {
55
- const trimmed = summary.trim()
56
- if (trimmed.length <= MAX_HANDOFF_SUMMARY_CHARS) return trimmed
57
- return [
58
- trimmed.slice(0, MAX_HANDOFF_SUMMARY_CHARS - 96).trimEnd(),
59
- '',
60
- '[handoff truncated to keep the resumed conversation responsive]',
61
- ].join('\n')
62
- }
@@ -1,122 +0,0 @@
1
- import { clearIdentity, getIdentityStatus } from '../storage/identity.js'
2
- import type { SlashContext, SlashResult } from './commands.js'
3
-
4
- export async function runMcp(args: string, ctx: SlashContext): Promise<SlashResult> {
5
- if (!ctx.mcp) {
6
- return { kind: 'note', variant: 'error', text: 'MCP runtime is not available in this session.' }
7
- }
8
-
9
- const tokens = args.trim().split(/\s+/).filter(Boolean)
10
- const sub = tokens[0]?.toLowerCase() ?? ''
11
- if (!sub || sub === 'status' || sub === 'list') {
12
- return { kind: 'note', text: ctx.mcp.renderStatus(), variant: 'info' }
13
- }
14
-
15
- try {
16
- if (sub === 'approve') {
17
- const name = tokens.slice(1).join(' ')
18
- if (!name) return { kind: 'note', variant: 'error', text: 'usage: /mcp approve <server>' }
19
- return { kind: 'note', text: await ctx.mcp.approveServer(name), variant: 'dim' }
20
- }
21
- if (sub === 'reject') {
22
- const name = tokens.slice(1).join(' ')
23
- if (!name) return { kind: 'note', variant: 'error', text: 'usage: /mcp reject <server>' }
24
- return { kind: 'note', text: await ctx.mcp.rejectServer(name), variant: 'dim' }
25
- }
26
- if (sub === 'reconnect') {
27
- const name = tokens.slice(1).join(' ') || undefined
28
- return { kind: 'note', text: await ctx.mcp.reconnect(name), variant: 'dim' }
29
- }
30
- if (sub === 'enable' || sub === 'disable') {
31
- const name = tokens.slice(1).join(' ')
32
- if (!name) return { kind: 'note', variant: 'error', text: `usage: /mcp ${sub} <server>` }
33
- return { kind: 'note', text: await ctx.mcp.setEnabled(name, sub === 'enable'), variant: 'dim' }
34
- }
35
- if (sub === 'add-json') {
36
- const project = tokens[1] === '--project'
37
- const nameIndex = project ? 2 : 1
38
- const name = tokens[nameIndex]
39
- if (!name) return { kind: 'note', variant: 'error', text: 'usage: /mcp add-json [--project] <name> <json>' }
40
- const jsonStart = nthTokenStart(args, nameIndex + 1)
41
- const json = jsonStart >= 0 ? args.slice(jsonStart).trim() : ''
42
- if (!json) return { kind: 'note', variant: 'error', text: 'usage: /mcp add-json [--project] <name> <json>' }
43
- return { kind: 'note', text: await ctx.mcp.addJson(name, json, project ? 'project' : 'user'), variant: 'dim' }
44
- }
45
- } catch (err: unknown) {
46
- return { kind: 'note', variant: 'error', text: `MCP failed: ${(err as Error).message}` }
47
- }
48
-
49
- return {
50
- kind: 'note',
51
- variant: 'error',
52
- text: 'usage: /mcp [status|approve <server>|reject <server>|reconnect [server]|enable <server>|disable <server>|add-json [--project] <name> <json>]',
53
- }
54
- }
55
-
56
- export async function runIdentity(args: string, ctx: SlashContext): Promise<SlashResult> {
57
- const tokens = args.trim().split(/\s+/).filter(Boolean)
58
- const sub = tokens[0]?.toLowerCase() ?? ''
59
- const rest = tokens.slice(1)
60
-
61
- if (!sub) {
62
- ctx.onIdentityRequest('manage')
63
- return { kind: 'handled' }
64
- }
65
-
66
- if (sub === 'status') {
67
- const status = await getIdentityStatus(ctx.config)
68
- if (!status) {
69
- return {
70
- kind: 'note',
71
- variant: 'dim',
72
- text: 'No Ethereum identity set. Run /identity create to make one.',
73
- }
74
- }
75
- const lines = [
76
- `address ${status.address}`,
77
- `updated ${status.createdAt}`,
78
- `wallet ${status.backend}`,
79
- ]
80
- if (status.source) lines.push(`registry ${status.source}`)
81
- if (status.agentId) lines.push(`agent #${status.agentId}`)
82
- return { kind: 'note', text: lines.join('\n') }
83
- }
84
-
85
- if (sub === 'create') {
86
- ctx.onIdentityRequest('create')
87
- return { kind: 'handled' }
88
- }
89
-
90
- if (sub === 'load') {
91
- ctx.onIdentityRequest('load')
92
- return { kind: 'handled' }
93
- }
94
-
95
- if (sub === 'remove') {
96
- if (rest[0] !== 'confirm') {
97
- return {
98
- kind: 'note',
99
- variant: 'error',
100
- text: 'Remove deletes local identity metadata and any legacy stored key. Re-run with: /identity remove confirm',
101
- }
102
- }
103
- const status = await getIdentityStatus(ctx.config)
104
- if (!status) {
105
- return { kind: 'note', variant: 'dim', text: 'No Ethereum identity to remove.' }
106
- }
107
- const next = await clearIdentity(ctx.config)
108
- ctx.onReplaceConfig(next)
109
- return { kind: 'note', text: `Removed identity ${status.address}.`, variant: 'dim' }
110
- }
111
-
112
- return {
113
- kind: 'note',
114
- variant: 'error',
115
- text: 'usage: /identity [status|create|load|remove confirm]',
116
- }
117
- }
118
-
119
- function nthTokenStart(value: string, tokenIndex: number): number {
120
- const matches = [...value.matchAll(/\S+/g)]
121
- return matches[tokenIndex]?.index ?? -1
122
- }
@@ -1,120 +0,0 @@
1
- import type { EthagentConfig, ProviderId } from '../storage/config.js'
2
- import { getConfigPath, localProviderBaseUrlFor } from '../storage/config.js'
3
- import { detectLlamaCpp } from '../models/llamacpp.js'
4
- import { detectSpec } from '../models/runtimeDetection.js'
5
- import { getIdentityStatus } from '../storage/identity.js'
6
- import { getLocalHfCacheDir } from '../models/huggingface.js'
7
- import { formatModelDisplayName } from '../models/modelDisplay.js'
8
- import { providerDisplayName } from '../models/modelPickerOptions.js'
9
- import type { ModelCatalogResult } from '../models/catalog.js'
10
- import type { SlashContext } from './commands.js'
11
-
12
- export function renderStatus(ctx: SlashContext): string {
13
- const elapsedMs = Date.now() - ctx.startedAt
14
- const minutes = Math.floor(elapsedMs / 60000)
15
- const seconds = Math.floor((elapsedMs % 60000) / 1000)
16
- const elapsed = minutes > 0 ? `${minutes}m${seconds.toString().padStart(2, '0')}s` : `${seconds}s`
17
- const displayModel = formatModelDisplayName(ctx.config.provider, ctx.config.model, { maxLength: 72 })
18
- return [
19
- `provider ${providerDisplayName(ctx.config.provider)}`,
20
- `model ${displayModel}`,
21
- `cwd ${ctx.cwd}`,
22
- `session ${ctx.sessionId.slice(0, 8)}`,
23
- 'state active',
24
- `turns ${ctx.turns}`,
25
- `tokens ~${ctx.approxTokens}`,
26
- `context ${ctx.contextUsage.percent}% (~${ctx.contextUsage.usedTokens}/${ctx.contextUsage.windowTokens}, ${ctx.contextUsage.source})`,
27
- `elapsed ${elapsed}`,
28
- ].join('\n')
29
- }
30
-
31
- export function renderContext(ctx: SlashContext): string {
32
- const usage = ctx.contextUsage
33
- const free = Math.max(0, usage.windowTokens - usage.usedTokens)
34
- const action =
35
- usage.percent >= 90
36
- ? 'Context is near the model limit. New requests will ask you to summarize into a new conversation, switch models, ignore and send, or cancel.'
37
- : usage.percent >= 75
38
- ? 'Context is getting full. Consider /compact before a new task boundary.'
39
- : 'Context has comfortable room.'
40
- return [
41
- 'context usage:',
42
- ` model ${providerDisplayName(ctx.config.provider)} · ${formatModelDisplayName(ctx.config.provider, ctx.config.model, { maxLength: 72 })}`,
43
- ` used ~${usage.usedTokens} / ${usage.windowTokens} tokens (${usage.percent}%)`,
44
- ` free ~${free} tokens`,
45
- ` estimate ${usage.confidence} (${usage.source})`,
46
- ' session active',
47
- '',
48
- action,
49
- ].join('\n')
50
- }
51
-
52
- export function renderDoctor(
53
- spec: Awaited<ReturnType<typeof detectSpec>>,
54
- keys: ReadonlyArray<readonly [ProviderId, boolean]>,
55
- identity: Awaited<ReturnType<typeof getIdentityStatus>>,
56
- ctx: SlashContext,
57
- llamaCpp: Awaited<ReturnType<typeof detectLlamaCpp>>,
58
- hfModelCount: number,
59
- ): string {
60
- const lines: string[] = ['diagnostics:']
61
- lines.push(` platform ${spec.platform}/${spec.arch}${spec.isAppleSilicon ? ' (apple silicon)' : ''}`)
62
- lines.push(` ram ${formatGB(spec.effectiveRamBytes)}${spec.gpuVramBytes ? ` · vram ${formatGB(spec.gpuVramBytes)}` : ''}`)
63
- lines.push(` local run ${llamaCpp.binaryPresent ? 'installed' : 'not installed'} · server ${llamaCpp.serverUp ? 'up' : 'down'}`)
64
- lines.push(` hf models ${hfModelCount} downloaded`)
65
- lines.push('')
66
- lines.push('config:')
67
- lines.push(` provider ${providerDisplayName(ctx.config.provider)}`)
68
- lines.push(` model ${formatModelDisplayName(ctx.config.provider, ctx.config.model, { maxLength: 72 })}`)
69
- if (ctx.config.baseUrl) lines.push(` baseUrl ${ctx.config.baseUrl}`)
70
- if (ctx.config.provider === 'llamacpp') lines.push(` hf cache ${getLocalHfCacheDir()}`)
71
- lines.push(` path ${getConfigPath()}`)
72
- lines.push('')
73
- lines.push('keys:')
74
- for (const [provider, present] of keys) {
75
- lines.push(` ${providerDisplayName(provider).padEnd(9)} ${present ? 'set' : 'not set'}`)
76
- }
77
- lines.push('')
78
- lines.push('identity:')
79
- if (identity) {
80
- lines.push(` address ${identity.address}`)
81
- lines.push(` backend ${identity.backend}`)
82
- if (identity.source) lines.push(` source ${identity.source}`)
83
- if (identity.agentId) lines.push(` token #${identity.agentId}`)
84
- } else {
85
- lines.push(' address not set')
86
- }
87
- return lines.join('\n')
88
- }
89
-
90
- export function renderModelCatalog(catalog: ModelCatalogResult, currentModel: string): string {
91
- const title = catalog.status === 'fallback'
92
- ? `${providerDisplayName(catalog.provider)} models (fallback${catalog.error ? `: ${catalog.error}` : ''}):`
93
- : `${providerDisplayName(catalog.provider)} models:`
94
- const lines = catalog.entries.map(entry => {
95
- const marker = entry.id === currentModel ? '*' : ' '
96
- const suffix = entry.source === 'fallback' ? ' fallback' : ''
97
- return `${marker} ${formatModelDisplayName(catalog.provider, entry.id, { maxLength: 72 })}${suffix}`
98
- })
99
- return [title, ...lines].join('\n')
100
- }
101
-
102
- export function baseUrlForModelSwitch(config: EthagentConfig): string | undefined {
103
- if (config.provider === 'llamacpp') return localProviderBaseUrlFor('llamacpp', config.baseUrl)
104
- if (config.provider === 'openai') return config.baseUrl
105
- return undefined
106
- }
107
-
108
- export function formatBytes(bytes: number): string {
109
- if (bytes <= 0) return '—'
110
- const gb = bytes / (1024 * 1024 * 1024)
111
- if (gb >= 1) return `${gb.toFixed(1)}GB`
112
- const mb = bytes / (1024 * 1024)
113
- return `${mb.toFixed(0)}MB`
114
- }
115
-
116
- function formatGB(bytes: number): string {
117
- const gb = bytes / (1024 * 1024 * 1024)
118
- if (gb >= 10) return `${Math.round(gb)}GB`
119
- return `${gb.toFixed(1)}GB`
120
- }