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,577 +0,0 @@
1
- import React, { useEffect, useRef, useState } from 'react'
2
- import { Box, Text } from 'ink'
3
- import { BrandSplash as Splash } from '../ui/BrandSplash.js'
4
- import { Select } from '../ui/Select.js'
5
- import { Spinner } from '../ui/Spinner.js'
6
- import { Surface } from '../ui/Surface.js'
7
- import { TextInput } from '../ui/TextInput.js'
8
- import { theme } from '../ui/theme.js'
9
- import { ModelPicker, type ModelPickerSelection } from '../models/ModelPicker.js'
10
- import { formatModelDisplayName } from '../models/modelDisplay.js'
11
- import { providerDisplayName } from '../models/providerDisplay.js'
12
- import { detectSpec, type SpecSnapshot } from '../models/runtimeDetection.js'
13
- import { FEATURED_HF_REPO_URL } from '../models/modelRecommendation.js'
14
- import { OPENAI_OAUTH_DEFAULT_MODEL } from '../models/catalog.js'
15
- import { OpenAIOAuthService } from '../auth/openaiOAuth/index.js'
16
- import { openExternalUrl } from '../utils/openExternal.js'
17
- import {
18
- loadConfig,
19
- saveConfigWithMerge,
20
- normalizeConfig,
21
- defaultModelFor,
22
- defaultBaseUrlFor,
23
- type EthagentConfig,
24
- type ProviderId,
25
- } from '../storage/config.js'
26
- import { setKey } from '../storage/secrets.js'
27
- import { IdentityHub, type IdentityHubResult } from '../identity/hub/IdentityHub.js'
28
- import { FirstRunTimeline, firstRunStageNumber, type FirstRunStepKind } from './FirstRunTimeline.js'
29
- import { configFromModelPickerSelection, formatGB } from './firstRunConfig.js'
30
-
31
- type Step =
32
- | { kind: 'detecting' }
33
- | { kind: 'detect-error'; message: string }
34
- | { kind: 'identity-start'; spec: SpecSnapshot }
35
- | { kind: 'identity-start-saving'; spec: SpecSnapshot; result: IdentityHubResult }
36
- | { kind: 'choose-path'; spec: SpecSnapshot }
37
- | { kind: 'hf-setup'; spec: SpecSnapshot }
38
- | { kind: 'cloud-provider' }
39
- | { kind: 'cloud-openai-auth' }
40
- | { kind: 'cloud-openai-oauth'; phase: 'waiting' | 'exchanging' | 'error'; url?: string; message?: string }
41
- | { kind: 'cloud-key'; provider: ProviderId; error?: string }
42
- | { kind: 'cloud-key-saving'; provider: ProviderId }
43
- | { kind: 'cloud-model'; provider: ProviderId }
44
- | { kind: 'saving'; config: EthagentConfig }
45
- | { kind: 'save-error'; config: EthagentConfig; message: string }
46
- | { kind: 'done'; config: EthagentConfig }
47
-
48
- type FirstRunProps = {
49
- onComplete: (config: EthagentConfig) => void
50
- onCancel: () => void
51
- }
52
-
53
- const TITLE: Record<string, string> = {
54
- 'detecting': 'Inspecting Machine',
55
- 'detect-error': 'Detection Failed',
56
- 'choose-path': 'Choose How To Run',
57
- 'hf-setup': 'Local Model',
58
- 'cloud-provider': 'Pick A Cloud Provider',
59
- 'cloud-openai-auth': 'Sign in with ChatGPT or API Key',
60
- 'cloud-openai-oauth': 'Sign in with ChatGPT',
61
- 'cloud-key': 'Paste API Key',
62
- 'cloud-key-saving': 'Storing Key',
63
- 'cloud-model': 'Pick A Model',
64
- 'saving': 'Saving Config',
65
- 'save-error': 'Save Failed',
66
- 'done': 'Ready',
67
- }
68
-
69
- const NAV_BACK = '↑↓ navigate · enter select · esc back'
70
- const NAV_CANCEL = '↑↓ navigate · enter select · esc cancel setup'
71
-
72
- export const FirstRun: React.FC<FirstRunProps> = ({ onComplete, onCancel }) => {
73
- const [step, setStep] = useState<Step>({ kind: 'detecting' })
74
- const [history, setHistory] = useState<Step[]>([])
75
- const [firstRunIdentity, setFirstRunIdentity] = useState<EthagentConfig['identity']>(undefined)
76
- const [firstRunConfig, setFirstRunConfig] = useState<EthagentConfig | undefined>(undefined)
77
- const oauthServiceRef = useRef<OpenAIOAuthService | null>(null)
78
-
79
- useEffect(() => {
80
- let cancelled = false
81
- loadConfig()
82
- .then(loaded => {
83
- if (cancelled || !loaded) return
84
- setFirstRunConfig(loaded)
85
- if (loaded.identity) setFirstRunIdentity(loaded.identity)
86
- })
87
- .catch(() => null)
88
- return () => { cancelled = true }
89
- }, [])
90
-
91
- const goTo = (next: Step): void => {
92
- setHistory(h => [...h, step])
93
- setStep(next)
94
- }
95
-
96
- const goBack = (): void => {
97
- if (history.length === 0) {
98
- onCancel()
99
- return
100
- }
101
- const prev = history[history.length - 1]!
102
- setStep(prev)
103
- setHistory(h => h.slice(0, -1))
104
- }
105
-
106
- useEffect(() => {
107
- let cancelled = false
108
- detectSpec()
109
- .then(spec => {
110
- if (cancelled) return
111
- setStep({ kind: 'identity-start', spec })
112
- })
113
- .catch((err: unknown) => {
114
- if (cancelled) return
115
- setStep({ kind: 'detect-error', message: (err as Error).message })
116
- })
117
- return () => { cancelled = true }
118
- }, [])
119
-
120
- useEffect(() => {
121
- if (step.kind !== 'saving') return
122
- let cancelled = false
123
- const incoming = normalizeConfig(step.config)
124
- ;(async () => {
125
- try {
126
- const merged = await saveConfigWithMerge(current => {
127
- if (!current) return incoming
128
- const baseErc = current.erc8004
129
- const incErc = incoming.erc8004
130
- const next: EthagentConfig = { ...current, ...incoming }
131
- if (baseErc || incErc) {
132
- const mergedVaults = {
133
- ...(baseErc?.operatorVaults ?? {}),
134
- ...(incErc?.operatorVaults ?? {}),
135
- }
136
- const erc = {
137
- ...(baseErc ?? {}),
138
- ...(incErc ?? {}),
139
- ...(Object.keys(mergedVaults).length > 0 ? { operatorVaults: mergedVaults } : {}),
140
- } as NonNullable<EthagentConfig['erc8004']>
141
- next.erc8004 = erc
142
- }
143
- return next
144
- })
145
- if (cancelled) return
146
- setStep({ kind: 'done', config: merged })
147
- onComplete(merged)
148
- } catch (err: unknown) {
149
- if (cancelled) return
150
- setStep({ kind: 'save-error', config: incoming, message: (err as Error).message })
151
- }
152
- })()
153
- return () => { cancelled = true }
154
- }, [step, onComplete])
155
-
156
- useEffect(() => {
157
- if (step.kind !== 'identity-start-saving') return
158
- let cancelled = false
159
- const persist = async (): Promise<void> => {
160
- if (step.result.kind === 'token') {
161
- setFirstRunIdentity(step.result.identity)
162
- }
163
- const reloaded = await loadConfig().catch(() => null)
164
- if (cancelled || !reloaded) return
165
- setFirstRunConfig(reloaded)
166
- }
167
- persist()
168
- .then(() => {
169
- if (cancelled) return
170
- setStep({ kind: 'choose-path', spec: step.spec })
171
- })
172
- .catch((err: unknown) => {
173
- if (cancelled) return
174
- setStep({
175
- kind: 'detect-error',
176
- message: `Could not store identity: ${(err as Error).message}`,
177
- })
178
- })
179
- return () => { cancelled = true }
180
- }, [step])
181
-
182
- const navHint = (canBack: boolean): string => canBack ? NAV_BACK : NAV_CANCEL
183
-
184
- const startFirstRunOpenAIOAuth = async (): Promise<void> => {
185
- oauthServiceRef.current?.cleanup()
186
- const service = new OpenAIOAuthService()
187
- oauthServiceRef.current = service
188
- goTo({ kind: 'cloud-openai-oauth', phase: 'waiting' })
189
- try {
190
- const result = await service.start(authUrl => {
191
- openExternalUrl(authUrl)
192
- setStep({ kind: 'cloud-openai-oauth', phase: 'waiting', url: authUrl })
193
- })
194
- if (oauthServiceRef.current !== service) return
195
- setStep({ kind: 'cloud-openai-oauth', phase: 'exchanging' })
196
- if (result.kind === 'apikey') {
197
- if (typeof result.apiKey !== 'string' || result.apiKey.length === 0) {
198
- throw new Error('OAuth result was apikey kind but no key was returned')
199
- }
200
- await setKey('openai', result.apiKey)
201
- }
202
- if (oauthServiceRef.current !== service) return
203
- oauthServiceRef.current = null
204
- if (result.kind === 'oauth-only') {
205
- setStep({
206
- kind: 'saving',
207
- config: withFirstRunIdentity({
208
- version: 1,
209
- provider: 'openai',
210
- model: OPENAI_OAUTH_DEFAULT_MODEL,
211
- firstRunAt: new Date().toISOString(),
212
- }),
213
- })
214
- return
215
- }
216
- setStep({ kind: 'cloud-model', provider: 'openai' })
217
- } catch (err: unknown) {
218
- if (oauthServiceRef.current !== service) return
219
- oauthServiceRef.current = null
220
- const message = err instanceof Error ? err.message : String(err)
221
- if (message === 'OpenAI sign-in was cancelled.') {
222
- setStep({ kind: 'cloud-openai-auth' })
223
- return
224
- }
225
- setStep({ kind: 'cloud-openai-oauth', phase: 'error', message })
226
- }
227
- }
228
-
229
- const withFirstRunIdentity = (config: EthagentConfig): EthagentConfig => {
230
- const merged: EthagentConfig = firstRunConfig
231
- ? {
232
- ...firstRunConfig,
233
- ...config,
234
- ...(firstRunConfig.erc8004 ? { erc8004: { ...firstRunConfig.erc8004, ...(config.erc8004 ?? {}) } } : config.erc8004 ? { erc8004: config.erc8004 } : {}),
235
- }
236
- : config
237
- return firstRunIdentity ? { ...merged, identity: firstRunIdentity } : merged
238
- }
239
-
240
- const renderShell = (
241
- currentKind: FirstRunStepKind,
242
- title: string,
243
- body: React.ReactNode,
244
- footer?: string,
245
- ): React.ReactElement => (
246
- <Box flexDirection="column" padding={1}>
247
- <Splash />
248
- <Box marginTop={1}>
249
- <Surface
250
- title={title}
251
- subtitle={<FirstRunTimeline current={firstRunStageNumber(currentKind)} />}
252
- footer={footer}
253
- >
254
- {body}
255
- </Surface>
256
- </Box>
257
- </Box>
258
- )
259
-
260
- const renderRaw = (
261
- currentKind: FirstRunStepKind,
262
- body: React.ReactNode,
263
- bodyOwnsTimeline = false,
264
- ): React.ReactElement => (
265
- <Box flexDirection="column" padding={1}>
266
- <Splash />
267
- {bodyOwnsTimeline ? null : (
268
- <Box marginTop={1} marginBottom={1}>
269
- <FirstRunTimeline current={firstRunStageNumber(currentKind)} />
270
- </Box>
271
- )}
272
- {body}
273
- </Box>
274
- )
275
-
276
- if (step.kind === 'detecting') {
277
- return renderShell(step.kind, TITLE['detecting']!, (
278
- <Text color={theme.dim}>Inspecting machine…</Text>
279
- ))
280
- }
281
-
282
- if (step.kind === 'detect-error') {
283
- return renderShell(step.kind, TITLE['detect-error']!, (
284
- <>
285
- <Text color={theme.accentError}>Could not inspect machine: {step.message}</Text>
286
- <Box marginTop={1}>
287
- <Select<'quit'>
288
- options={[{ value: 'quit', label: 'quit' }]}
289
- onSubmit={onCancel}
290
- onCancel={onCancel}
291
- />
292
- </Box>
293
- </>
294
- ))
295
- }
296
-
297
- if (step.kind === 'identity-start') {
298
- return renderRaw(step.kind, (
299
- <IdentityHub
300
- mode="first-run"
301
- config={firstRunConfig}
302
- onConfigChange={setFirstRunConfig}
303
- onComplete={result => {
304
- if (result.kind === 'cancel') {
305
- onCancel()
306
- return
307
- }
308
- if (result.kind === 'skip') {
309
- setStep({ kind: 'choose-path', spec: step.spec })
310
- return
311
- }
312
- setStep({ kind: 'identity-start-saving', spec: step.spec, result })
313
- }}
314
- />
315
- ), true)
316
- }
317
-
318
- if (step.kind === 'identity-start-saving') {
319
- return renderShell(step.kind, 'Storing Identity', (
320
- <Text color={theme.dim}>Storing identity...</Text>
321
- ))
322
- }
323
-
324
- if (step.kind === 'choose-path') {
325
- const { spec } = step
326
- return renderShell(step.kind, TITLE['choose-path']!, (
327
- <>
328
- <Box flexDirection="column" marginBottom={1}>
329
- <Text color={theme.dim}>
330
- detected {formatGB(spec.effectiveRamBytes)} RAM
331
- {spec.gpuVramBytes ? `, ${formatGB(spec.gpuVramBytes)} VRAM` : ''}
332
- {spec.isAppleSilicon ? ', Apple Silicon' : ''}
333
- </Text>
334
- </Box>
335
- <Select<'hf' | 'cloud'>
336
- options={[
337
- { value: 'hf', label: 'Local Model', hint: 'Download and run locally' },
338
- { value: 'cloud', label: 'Cloud API', hint: 'OpenAI, Anthropic, or Gemini' },
339
- ]}
340
- hintLayout="inline"
341
- onSubmit={choice => {
342
- if (choice === 'cloud') goTo({ kind: 'cloud-provider' })
343
- else goTo({ kind: 'hf-setup', spec })
344
- }}
345
- onCancel={onCancel}
346
- />
347
- </>
348
- ), navHint(false))
349
- }
350
-
351
- if (step.kind === 'hf-setup') {
352
- const modelPickerConfig: EthagentConfig = withFirstRunIdentity({
353
- version: 1,
354
- provider: 'llamacpp',
355
- model: defaultModelFor('llamacpp'),
356
- baseUrl: defaultBaseUrlFor('llamacpp'),
357
- firstRunAt: new Date().toISOString(),
358
- })
359
- return renderRaw(step.kind, (
360
- <ModelPicker
361
- currentConfig={modelPickerConfig}
362
- currentProvider="llamacpp"
363
- currentModel={modelPickerConfig.model}
364
- featuredHfRepo={FEATURED_HF_REPO_URL}
365
- localOnly
366
- onPick={(selection: ModelPickerSelection) => {
367
- goTo({ kind: 'saving', config: configFromModelPickerSelection(selection, modelPickerConfig) })
368
- }}
369
- onCancel={goBack}
370
- />
371
- ))
372
- }
373
-
374
- if (step.kind === 'cloud-provider') {
375
- return renderShell(step.kind, TITLE['cloud-provider']!, (
376
- <Select<ProviderId>
377
- options={[
378
- { value: 'openai', label: 'OpenAI' },
379
- { value: 'anthropic', label: 'Anthropic' },
380
- { value: 'gemini', label: 'Gemini' },
381
- ]}
382
- onSubmit={provider => {
383
- if (provider === 'openai') goTo({ kind: 'cloud-openai-auth' })
384
- else goTo({ kind: 'cloud-key', provider })
385
- }}
386
- onCancel={goBack}
387
- />
388
- ), navHint(true))
389
- }
390
-
391
- if (step.kind === 'cloud-openai-auth') {
392
- return renderShell(step.kind, TITLE['cloud-openai-auth']!, (
393
- <Select<'oauth' | 'apikey'>
394
- options={[
395
- { value: 'oauth', role: 'section', label: 'Recommended' },
396
- { value: 'oauth', label: 'Sign in with ChatGPT', hint: 'Use your ChatGPT subscription', bold: true },
397
- { value: 'apikey', role: 'section', label: 'Alternative' },
398
- { value: 'apikey', label: 'Paste API Key', hint: 'Use an OpenAI platform key', role: 'utility' },
399
- ]}
400
- hintLayout="inline"
401
- onSubmit={choice => {
402
- if (choice === 'oauth') void startFirstRunOpenAIOAuth()
403
- else goTo({ kind: 'cloud-key', provider: 'openai' })
404
- }}
405
- onCancel={goBack}
406
- />
407
- ), navHint(true))
408
- }
409
-
410
- if (step.kind === 'cloud-openai-oauth') {
411
- if (step.phase === 'error') {
412
- return renderShell(step.kind, TITLE['cloud-openai-oauth']!, (
413
- <>
414
- <Text color={theme.accentError}>{step.message ?? 'Sign-in did not complete.'}</Text>
415
- <Box marginTop={1}>
416
- <Select<'retry' | 'apikey' | 'back'>
417
- options={[
418
- { value: 'retry', role: 'section', label: 'Recovery' },
419
- { value: 'retry', label: 'Try Again', hint: 'Reopen the browser sign-in flow' },
420
- { value: 'apikey', label: 'Use API Key Instead', hint: 'Paste an OpenAI platform key' },
421
- { value: 'back', role: 'section', label: 'Navigation' },
422
- { value: 'back', label: 'Back', hint: 'Return to sign-in choice', role: 'utility' },
423
- ]}
424
- hintLayout="inline"
425
- onSubmit={choice => {
426
- if (choice === 'retry') void startFirstRunOpenAIOAuth()
427
- else if (choice === 'apikey') goTo({ kind: 'cloud-key', provider: 'openai' })
428
- else goTo({ kind: 'cloud-openai-auth' })
429
- }}
430
- onCancel={() => goTo({ kind: 'cloud-openai-auth' })}
431
- />
432
- </Box>
433
- </>
434
- ), navHint(true))
435
- }
436
- if (step.phase === 'exchanging') {
437
- return renderShell(step.kind, TITLE['cloud-openai-oauth']!, (
438
- <Box marginTop={1}>
439
- <Spinner label="completing sign-in..." />
440
- </Box>
441
- ))
442
- }
443
- return renderShell(step.kind, TITLE['cloud-openai-oauth']!, (
444
- <>
445
- <Spinner label="waiting for browser sign-in..." />
446
- {step.url ? (
447
- <Box flexDirection="column" marginTop={1}>
448
- <Text color={theme.dim}>If the browser did not open, visit:</Text>
449
- <Text color={theme.dim}>{step.url}</Text>
450
- </Box>
451
- ) : null}
452
- <Box marginTop={1}>
453
- <Select<'cancel'>
454
- options={[{ value: 'cancel', label: 'Cancel Sign-in', role: 'utility' }]}
455
- hintLayout="inline"
456
- onSubmit={() => {
457
- oauthServiceRef.current?.cleanup()
458
- oauthServiceRef.current = null
459
- goTo({ kind: 'cloud-openai-auth' })
460
- }}
461
- onCancel={() => {
462
- oauthServiceRef.current?.cleanup()
463
- oauthServiceRef.current = null
464
- goTo({ kind: 'cloud-openai-auth' })
465
- }}
466
- />
467
- </Box>
468
- </>
469
- ))
470
- }
471
-
472
- if (step.kind === 'cloud-key' || step.kind === 'cloud-key-saving') {
473
- const provider = step.provider
474
- const saving = step.kind === 'cloud-key-saving'
475
- const error = step.kind === 'cloud-key' ? step.error : undefined
476
- const keyTitle = `${TITLE[saving ? 'cloud-key-saving' : 'cloud-key']!} · ${providerDisplayName(provider)}`
477
- return renderShell(step.kind, keyTitle, (
478
- <>
479
- <Text color={theme.dim}>stored in your OS keyring when available; never written to config.json</Text>
480
- {error ? <Text color={theme.accentError}>{error}</Text> : null}
481
- <Box marginTop={1}>
482
- <TextInput
483
- isSecret
484
- placeholder={provider === 'openai' ? 'sk-...' : 'paste key and press enter'}
485
- chromeWidth={4}
486
- validate={v => v.trim().length >= 8 ? null : 'key looks too short'}
487
- onSubmit={async value => {
488
- const trimmed = value.trim()
489
- setHistory(h => [...h, { kind: 'cloud-key', provider }])
490
- setStep({ kind: 'cloud-key-saving', provider })
491
- try {
492
- await setKey(provider, trimmed)
493
- setStep({ kind: 'cloud-model', provider })
494
- } catch (err: unknown) {
495
- setHistory(h => h.slice(0, -1))
496
- setStep({
497
- kind: 'cloud-key',
498
- provider,
499
- error: `could not store key: ${(err as Error).message}`,
500
- })
501
- }
502
- }}
503
- onCancel={goBack}
504
- />
505
- </Box>
506
- {saving ? <Text color={theme.dim}>storing key…</Text> : null}
507
- </>
508
- ), saving ? undefined : navHint(true))
509
- }
510
-
511
- if (step.kind === 'cloud-model') {
512
- const { provider } = step
513
- const defaultModel = defaultModelFor(provider)
514
- return renderShell(step.kind, TITLE['cloud-model']!, (
515
- <>
516
- <Text color={theme.dim}>press enter to accept default: {defaultModel}</Text>
517
- <Box marginTop={1}>
518
- <TextInput
519
- initialValue={defaultModel}
520
- placeholder={defaultModel}
521
- chromeWidth={4}
522
- onSubmit={model => goTo({
523
- kind: 'saving',
524
- config: withFirstRunIdentity({
525
- version: 1,
526
- provider,
527
- model: model.trim() || defaultModel,
528
- firstRunAt: new Date().toISOString(),
529
- }),
530
- })}
531
- onCancel={goBack}
532
- />
533
- </Box>
534
- </>
535
- ), navHint(true))
536
- }
537
-
538
- if (step.kind === 'saving') {
539
- return renderShell(step.kind, TITLE['saving']!, (
540
- <Text color={theme.dim}>saving config…</Text>
541
- ))
542
- }
543
-
544
- if (step.kind === 'save-error') {
545
- return renderShell(step.kind, TITLE['save-error']!, (
546
- <>
547
- <Text color={theme.accentError}>{step.message}</Text>
548
- <Box marginTop={1}>
549
- <Select<'retry' | 'back' | 'quit'>
550
- options={[
551
- { value: 'retry', label: 'retry save' },
552
- { value: 'back', label: 'go back and edit' },
553
- { value: 'quit', label: 'quit setup' },
554
- ]}
555
- onSubmit={choice => {
556
- if (choice === 'retry') setStep({ kind: 'saving', config: step.config })
557
- else if (choice === 'back') goBack()
558
- else onCancel()
559
- }}
560
- onCancel={goBack}
561
- />
562
- </Box>
563
- </>
564
- ), navHint(history.length > 0))
565
- }
566
-
567
- if (step.kind === 'done') {
568
- return renderShell(step.kind, TITLE['done']!, (
569
- <Text color={theme.accentPeriwinkle}>
570
- Ready · {providerDisplayName(step.config.provider)} · {formatModelDisplayName(step.config.provider, step.config.model, { maxLength: 48 })}
571
- </Text>
572
- ))
573
- }
574
-
575
- return null
576
- }
577
-
@@ -1,51 +0,0 @@
1
- import React from 'react'
2
- import { FlowTimeline } from '../identity/hub/shared/components/FlowTimeline.js'
3
-
4
- export const FIRST_RUN_STAGES = ['Inspect', 'Identity', 'Model'] as const
5
-
6
- export type FirstRunStepKind =
7
- | 'detecting'
8
- | 'detect-error'
9
- | 'identity-start'
10
- | 'identity-start-saving'
11
- | 'choose-path'
12
- | 'hf-setup'
13
- | 'cloud-provider'
14
- | 'cloud-openai-auth'
15
- | 'cloud-openai-oauth'
16
- | 'cloud-key'
17
- | 'cloud-key-saving'
18
- | 'cloud-model'
19
- | 'saving'
20
- | 'save-error'
21
- | 'done'
22
-
23
- export function firstRunStageNumber(stepKind: FirstRunStepKind): number {
24
- switch (stepKind) {
25
- case 'detecting':
26
- case 'detect-error':
27
- return 1
28
- case 'identity-start':
29
- case 'identity-start-saving':
30
- return 2
31
- case 'choose-path':
32
- case 'hf-setup':
33
- case 'cloud-provider':
34
- case 'cloud-openai-auth':
35
- case 'cloud-openai-oauth':
36
- case 'cloud-key':
37
- case 'cloud-key-saving':
38
- case 'cloud-model':
39
- return 3
40
- case 'saving':
41
- case 'save-error':
42
- case 'done':
43
- return 4
44
- default:
45
- return 0
46
- }
47
- }
48
-
49
- export const FirstRunTimeline: React.FC<{ current: number }> = ({ current }) => (
50
- <FlowTimeline steps={[...FIRST_RUN_STAGES]} current={current} />
51
- )
@@ -1,26 +0,0 @@
1
- import { defaultBaseUrlFor, type EthagentConfig } from '../storage/config.js'
2
- import type { ModelPickerSelection } from '../models/ModelPicker.js'
3
-
4
- export function configFromModelPickerSelection(selection: ModelPickerSelection, base: EthagentConfig): EthagentConfig {
5
- if (selection.kind === 'llamacpp') {
6
- return {
7
- ...base,
8
- provider: 'llamacpp',
9
- model: selection.model,
10
- baseUrl: defaultBaseUrlFor('llamacpp'),
11
- localMmprojPath: selection.mmprojPath,
12
- }
13
- }
14
- return {
15
- ...base,
16
- provider: selection.provider,
17
- model: selection.model,
18
- baseUrl: undefined,
19
- localMmprojPath: undefined,
20
- }
21
- }
22
-
23
- export function formatGB(bytes: number): string {
24
- const gb = bytes / (1024 * 1024 * 1024)
25
- return gb < 10 ? `${gb.toFixed(1)}GB` : `${Math.round(gb)}GB`
26
- }
@@ -1,22 +0,0 @@
1
- import { useCallback } from 'react'
2
- import { useKeybinding } from '../keybindings/KeybindingProvider.js'
3
-
4
- type Options = {
5
- abortSignal?: AbortSignal
6
- onCancel: () => void
7
- isActive?: boolean
8
- }
9
-
10
- export function useCancelRequest({ abortSignal, onCancel, isActive = true }: Options): void {
11
- const canCancel = abortSignal !== undefined && !abortSignal.aborted
12
-
13
- const handleCancel = useCallback(() => {
14
- if (!canCancel) return
15
- onCancel()
16
- }, [canCancel, onCancel])
17
-
18
- useKeybinding('chat:cancel', handleCancel, {
19
- context: 'Chat',
20
- isActive: isActive && canCancel,
21
- })
22
- }