ethagent 1.1.1 → 2.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 (271) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +127 -29
  3. package/package.json +16 -9
  4. package/src/app/FirstRun.tsx +192 -146
  5. package/src/app/FirstRunTimeline.tsx +47 -0
  6. package/src/app/input/AppInputProvider.tsx +1 -1
  7. package/src/app/keybindings/KeybindingProvider.tsx +1 -1
  8. package/src/chat/ChatBottomPane.tsx +0 -1
  9. package/src/chat/ChatInput.tsx +6 -6
  10. package/src/chat/ChatScreen.tsx +43 -18
  11. package/src/chat/ContextLimitView.tsx +4 -4
  12. package/src/chat/ContinuityEditReviewView.tsx +11 -17
  13. package/src/chat/ConversationStack.tsx +3 -0
  14. package/src/chat/CopyPicker.tsx +0 -1
  15. package/src/chat/MessageList.tsx +62 -45
  16. package/src/chat/PermissionPrompt.tsx +13 -9
  17. package/src/chat/PlanApprovalView.tsx +3 -3
  18. package/src/chat/ResumeView.tsx +1 -4
  19. package/src/chat/RewindView.tsx +2 -2
  20. package/src/chat/TranscriptView.tsx +6 -0
  21. package/src/chat/chatInputState.ts +1 -1
  22. package/src/chat/chatScreenUtils.ts +22 -11
  23. package/src/chat/chatSessionState.ts +2 -2
  24. package/src/chat/chatTurnOrchestrator.ts +16 -81
  25. package/src/chat/commands.ts +1 -1
  26. package/src/chat/textCursor.ts +1 -1
  27. package/src/chat/transcriptViewport.ts +2 -7
  28. package/src/cli/ResetConfirmView.tsx +1 -1
  29. package/src/cli/main.tsx +9 -3
  30. package/src/cli/preview.tsx +0 -5
  31. package/src/cli/updateNotice.ts +5 -3
  32. package/src/identity/continuity/editor.ts +7 -107
  33. package/src/identity/continuity/envelope.ts +1048 -40
  34. package/src/identity/continuity/history.ts +4 -4
  35. package/src/identity/continuity/localBackup.ts +249 -0
  36. package/src/identity/continuity/privateEdit/apply.ts +170 -0
  37. package/src/identity/continuity/privateEdit/diff.ts +82 -0
  38. package/src/identity/continuity/privateEdit/files.ts +23 -0
  39. package/src/identity/continuity/privateEdit/types.ts +28 -0
  40. package/src/identity/continuity/privateEdit.ts +10 -298
  41. package/src/identity/continuity/publicSkills.ts +8 -9
  42. package/src/identity/continuity/snapshots.ts +17 -6
  43. package/src/identity/continuity/storage/defaults.ts +111 -0
  44. package/src/identity/continuity/storage/files.ts +72 -0
  45. package/src/identity/continuity/storage/markdown.ts +81 -0
  46. package/src/identity/continuity/storage/paths.ts +24 -0
  47. package/src/identity/continuity/storage/scaffold.ts +124 -0
  48. package/src/identity/continuity/storage/status.ts +86 -0
  49. package/src/identity/continuity/storage/types.ts +27 -0
  50. package/src/identity/continuity/storage.ts +32 -507
  51. package/src/identity/continuity/zipWriter.ts +95 -0
  52. package/src/identity/crypto/backupEnvelope.ts +14 -247
  53. package/src/identity/crypto/eth.ts +7 -7
  54. package/src/identity/ens/agentRecords.ts +96 -0
  55. package/src/identity/ens/ensAutomation/contracts.ts +38 -0
  56. package/src/identity/ens/ensAutomation/delete.ts +80 -0
  57. package/src/identity/ens/ensAutomation/names.ts +14 -0
  58. package/src/identity/ens/ensAutomation/operators.ts +29 -0
  59. package/src/identity/ens/ensAutomation/read.ts +114 -0
  60. package/src/identity/ens/ensAutomation/root.ts +63 -0
  61. package/src/identity/ens/ensAutomation/setup.ts +284 -0
  62. package/src/identity/ens/ensAutomation/transactions.ts +107 -0
  63. package/src/identity/ens/ensAutomation/types.ts +126 -0
  64. package/src/identity/ens/ensAutomation.ts +29 -0
  65. package/src/identity/ens/ensLookup/client.ts +43 -0
  66. package/src/identity/ens/ensLookup/constants.ts +26 -0
  67. package/src/identity/ens/ensLookup/discovery.ts +70 -0
  68. package/src/identity/ens/ensLookup/names.ts +34 -0
  69. package/src/identity/ens/ensLookup/records.ts +45 -0
  70. package/src/identity/ens/ensLookup/resolve.ts +75 -0
  71. package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
  72. package/src/identity/ens/ensLookup/types.ts +38 -0
  73. package/src/identity/ens/ensLookup/validation.ts +72 -0
  74. package/src/identity/ens/ensLookup.ts +19 -0
  75. package/src/identity/ens/ensRegistration.ts +199 -0
  76. package/src/identity/ens/resolverDelegation.ts +48 -0
  77. package/src/identity/hub/IdentityHub.tsx +13 -815
  78. package/src/identity/hub/OperationalRoutes.tsx +370 -0
  79. package/src/identity/hub/Routes.tsx +361 -0
  80. package/src/identity/hub/advancedEnsValidation.ts +45 -0
  81. package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
  82. package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
  83. package/src/identity/hub/components/FlowTimeline.tsx +27 -0
  84. package/src/identity/hub/components/IdentitySummary.tsx +190 -0
  85. package/src/identity/hub/components/MenuScreen.tsx +237 -0
  86. package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
  87. package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
  88. package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
  89. package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
  90. package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
  91. package/src/identity/hub/effects/create.ts +310 -0
  92. package/src/identity/hub/effects/ens/flows.ts +218 -0
  93. package/src/identity/hub/effects/ens/index.ts +11 -0
  94. package/src/identity/hub/effects/ens/transactions.ts +239 -0
  95. package/src/identity/hub/effects/index.ts +74 -0
  96. package/src/identity/hub/effects/profile/profileState.ts +173 -0
  97. package/src/identity/hub/effects/publicProfile/index.ts +5 -0
  98. package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
  99. package/src/identity/hub/effects/rebackup/index.ts +7 -0
  100. package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
  101. package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
  102. package/src/identity/hub/effects/receipts.ts +46 -0
  103. package/src/identity/hub/effects/restore/apply.ts +112 -0
  104. package/src/identity/hub/effects/restore/auth.ts +159 -0
  105. package/src/identity/hub/effects/restore/discover.ts +86 -0
  106. package/src/identity/hub/effects/restore/envelopes.ts +21 -0
  107. package/src/identity/hub/effects/restore/fetch.ts +25 -0
  108. package/src/identity/hub/effects/restore/index.ts +22 -0
  109. package/src/identity/hub/effects/restore/recovery.ts +135 -0
  110. package/src/identity/hub/effects/restore/resolve.ts +102 -0
  111. package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
  112. package/src/identity/hub/effects/restore/shared.ts +91 -0
  113. package/src/identity/hub/effects/restoreAdmin.ts +93 -0
  114. package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
  115. package/src/identity/hub/effects/shared/snapshot.ts +336 -0
  116. package/src/identity/hub/effects/shared/sync.ts +190 -0
  117. package/src/identity/hub/effects/token-transfer/index.ts +6 -0
  118. package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
  119. package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
  120. package/src/identity/hub/effects/types.ts +53 -0
  121. package/src/identity/hub/effects/vault/preflight.ts +50 -0
  122. package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
  123. package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
  124. package/src/identity/hub/flows/continuity/RecoveryConfirmScreen.tsx +104 -0
  125. package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
  126. package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
  127. package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
  128. package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
  129. package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
  130. package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
  131. package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
  132. package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
  133. package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
  134. package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
  135. package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
  136. package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
  137. package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
  138. package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
  139. package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
  140. package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
  141. package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
  142. package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
  143. package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
  144. package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
  145. package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
  146. package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
  147. package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
  148. package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
  149. package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +25 -43
  150. package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
  151. package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
  152. package/src/identity/hub/identityHubReducer.ts +166 -101
  153. package/src/identity/hub/model/continuity.ts +94 -0
  154. package/src/identity/hub/model/copy.ts +35 -0
  155. package/src/identity/hub/model/custody.ts +54 -0
  156. package/src/identity/hub/model/ens.ts +49 -0
  157. package/src/identity/hub/model/errors.ts +140 -0
  158. package/src/identity/hub/model/format.ts +15 -0
  159. package/src/identity/hub/model/identity.ts +94 -0
  160. package/src/identity/hub/model/network.ts +32 -0
  161. package/src/identity/hub/model/transfer.ts +57 -0
  162. package/src/identity/hub/operatorWallets.ts +131 -0
  163. package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
  164. package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
  165. package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
  166. package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
  167. package/src/identity/hub/reconciliation/index.ts +21 -0
  168. package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
  169. package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
  170. package/src/identity/hub/txGuard.ts +51 -0
  171. package/src/identity/hub/types.ts +17 -0
  172. package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
  173. package/src/identity/hub/useIdentityHubController.ts +396 -0
  174. package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
  175. package/src/identity/hub/utils.ts +79 -0
  176. package/src/identity/identityCompat.ts +34 -0
  177. package/src/identity/profile/agentIcon.ts +61 -0
  178. package/src/identity/profile/imagePicker.ts +12 -12
  179. package/src/identity/registry/erc8004/abi.ts +14 -0
  180. package/src/identity/registry/erc8004/chains.ts +150 -0
  181. package/src/identity/registry/erc8004/client.ts +11 -0
  182. package/src/identity/registry/erc8004/discovery.ts +511 -0
  183. package/src/identity/registry/erc8004/metadata.ts +335 -0
  184. package/src/identity/registry/erc8004/ownership.ts +121 -0
  185. package/src/identity/registry/erc8004/preflight.ts +123 -0
  186. package/src/identity/registry/erc8004/transactions.ts +77 -0
  187. package/src/identity/registry/erc8004/types.ts +88 -0
  188. package/src/identity/registry/erc8004/uri.ts +59 -0
  189. package/src/identity/registry/erc8004/utils.ts +58 -0
  190. package/src/identity/registry/erc8004.ts +53 -1106
  191. package/src/identity/registry/fieldParsers.ts +28 -0
  192. package/src/identity/registry/operatorVault/bytecode.ts +98 -0
  193. package/src/identity/registry/operatorVault/constants.ts +38 -0
  194. package/src/identity/registry/operatorVault/read.ts +246 -0
  195. package/src/identity/registry/operatorVault/transactions.ts +81 -0
  196. package/src/identity/registry/operatorVault.ts +44 -0
  197. package/src/identity/storage/ipfs.ts +26 -24
  198. package/src/identity/wallet/browserWallet/gas.ts +41 -0
  199. package/src/identity/wallet/browserWallet/html.ts +106 -0
  200. package/src/identity/wallet/browserWallet/http.ts +28 -0
  201. package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
  202. package/src/identity/wallet/browserWallet/requests.ts +191 -0
  203. package/src/identity/wallet/browserWallet/session.ts +325 -0
  204. package/src/identity/wallet/browserWallet/types.ts +192 -0
  205. package/src/identity/wallet/browserWallet/validation.ts +74 -0
  206. package/src/identity/wallet/browserWallet.ts +30 -393
  207. package/src/identity/wallet/page/constants.ts +5 -0
  208. package/src/identity/wallet/page/controller.ts +251 -0
  209. package/src/identity/wallet/page/copy.ts +340 -0
  210. package/src/identity/wallet/page/grainient.ts +278 -0
  211. package/src/identity/wallet/page/html.ts +28 -0
  212. package/src/identity/wallet/page/markup.ts +50 -0
  213. package/src/identity/wallet/page/state.ts +9 -0
  214. package/src/identity/wallet/page/styles/base.ts +259 -0
  215. package/src/identity/wallet/page/styles/components.ts +262 -0
  216. package/src/identity/wallet/page/styles/index.ts +5 -0
  217. package/src/identity/wallet/page/styles/responsive.ts +247 -0
  218. package/src/identity/wallet/page/types.ts +47 -0
  219. package/src/identity/wallet/page/view.ts +535 -0
  220. package/src/identity/wallet/page/walletProvider.ts +70 -0
  221. package/src/identity/wallet/page.tsx +38 -0
  222. package/src/identity/wallet/walletPurposeCompat.ts +27 -0
  223. package/src/mcp/manager.ts +0 -1
  224. package/src/models/ModelPicker.tsx +36 -30
  225. package/src/models/catalog.ts +5 -2
  226. package/src/models/huggingface.ts +9 -9
  227. package/src/models/llamacpp.ts +13 -13
  228. package/src/models/modelDisplay.ts +75 -0
  229. package/src/models/modelPickerOptions.ts +16 -3
  230. package/src/models/modelRecommendation.ts +0 -1
  231. package/src/providers/errors.ts +16 -0
  232. package/src/providers/gemini.ts +252 -39
  233. package/src/providers/registry.ts +2 -2
  234. package/src/providers/retry.ts +1 -1
  235. package/src/runtime/sessionMode.ts +1 -1
  236. package/src/runtime/systemPrompt.ts +2 -0
  237. package/src/runtime/toolExecution.ts +18 -22
  238. package/src/runtime/toolIntent.ts +0 -20
  239. package/src/runtime/turn.ts +0 -92
  240. package/src/storage/atomicWrite.ts +4 -1
  241. package/src/storage/config.ts +181 -5
  242. package/src/storage/identity.ts +9 -3
  243. package/src/storage/secrets.ts +2 -2
  244. package/src/tools/bashSafety.ts +8 -0
  245. package/src/tools/changeDirectoryTool.ts +1 -1
  246. package/src/tools/deleteFileTool.ts +4 -4
  247. package/src/tools/editTool.ts +4 -4
  248. package/src/tools/editUtils.ts +5 -5
  249. package/src/tools/privateContinuityEditTool.ts +4 -5
  250. package/src/tools/privateContinuityReadTool.ts +1 -2
  251. package/src/tools/registry.ts +30 -0
  252. package/src/tools/writeFileTool.ts +5 -5
  253. package/src/ui/BrandSplash.tsx +20 -85
  254. package/src/ui/ProgressBar.tsx +3 -5
  255. package/src/ui/Select.tsx +21 -9
  256. package/src/ui/Spinner.tsx +38 -3
  257. package/src/ui/Surface.tsx +3 -3
  258. package/src/ui/TextInput.tsx +191 -29
  259. package/src/ui/theme.ts +7 -34
  260. package/src/utils/openExternal.ts +21 -0
  261. package/src/utils/withRetry.ts +47 -3
  262. package/src/identity/hub/identityHubEffects.ts +0 -937
  263. package/src/identity/hub/identityHubModel.ts +0 -291
  264. package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -144
  265. package/src/identity/hub/screens/EditProfileFlow.tsx +0 -145
  266. package/src/identity/hub/screens/IdentitySummary.tsx +0 -90
  267. package/src/identity/hub/screens/MenuScreen.tsx +0 -117
  268. package/src/identity/hub/screens/RecoveryConfirmScreen.tsx +0 -87
  269. package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
  270. package/src/identity/wallet/wallet-page/wallet.html +0 -1202
  271. /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
@@ -0,0 +1,361 @@
1
+ import React from 'react'
2
+ import { Box, Text } from 'ink'
3
+ import { getAddress, isAddress } from 'viem'
4
+ import { Surface } from '../../ui/Surface.js'
5
+ import { Select } from '../../ui/Select.js'
6
+ import { theme } from '../../ui/theme.js'
7
+ import type { SelectableNetwork } from '../../storage/config.js'
8
+ import { copyToClipboard } from '../../utils/clipboard.js'
9
+ import { DEFAULT_IPFS_API_URL } from '../storage/ipfs.js'
10
+ import { chainIdForNetwork, erc8004ConfigForSupportedChain } from '../registry/erc8004.js'
11
+ import { shortAddress } from './model/format.js'
12
+ import {
13
+ canRestoreCandidate,
14
+ resolveAgentEnsToCandidate,
15
+ resolveAgentTokenIdToCandidate,
16
+ } from './effects/restore/index.js'
17
+ import {
18
+ runRegistrySubmit,
19
+ runStorageSubmit,
20
+ } from './effects/create.js'
21
+ import {
22
+ runRestoreRegistrySubmit,
23
+ } from './effects/restoreAdmin.js'
24
+ import { MenuScreen } from './components/MenuScreen.js'
25
+ import { CreateFlow } from './flows/create/CreateFlow.js'
26
+ import { RestoreFlow } from './flows/restore/RestoreFlow.js'
27
+ import { NetworkScreen } from './components/NetworkScreen.js'
28
+ import { DetailsScreen } from './components/DetailsScreen.js'
29
+ import {
30
+ IdentityHubTokenTransferFlow,
31
+ isTokenTransferStep,
32
+ } from './flows/token-transfer/IdentityHubTokenTransferFlow.js'
33
+ import {
34
+ chainLabel,
35
+ isCreateStep,
36
+ isRestoreStep,
37
+ } from './utils.js'
38
+ import { IdentityHubOperationalRoutes } from './OperationalRoutes.js'
39
+ import type { IdentityHubController } from './useIdentityHubController.js'
40
+
41
+ export const IdentityHubRoutes: React.FC<{ controller: IdentityHubController }> = ({ controller }) => {
42
+ const {
43
+ mode,
44
+ config,
45
+ onComplete,
46
+ onConfigChange,
47
+ identity,
48
+ reconciliation,
49
+ step,
50
+ walletSession,
51
+ restoreProgress,
52
+ tokenTransferProgress,
53
+ copyNotice,
54
+ canRebackup,
55
+ callbacks,
56
+ workingStatus,
57
+ setStep,
58
+ back,
59
+ closeHub,
60
+ setCopyNotice,
61
+ handleStepError,
62
+ resolveRegistryForIdentity,
63
+ finishFirstRunIdentity,
64
+ openEnsEdit,
65
+ openTokenTransferFlow,
66
+ } = controller
67
+
68
+ const footer = <Text color={theme.dim}>enter select · esc back</Text>
69
+
70
+ if (step.kind === 'first-run-ens-prompt') {
71
+ const tokenLabel = step.identity.agentId ? `#${step.identity.agentId}` : ''
72
+ return (
73
+ <Surface
74
+ title="Token Minted"
75
+ subtitle={`Agent token ${tokenLabel} is live on ${chainLabel(step.registry.chainId)}. Optional next step: link an ENS subdomain so others find this agent by name.`}
76
+ footer={footer}
77
+ >
78
+ <Box flexDirection="column">
79
+ <Text color={theme.textSubtle}>An ENS subdomain like agent.example.eth makes the agent discoverable without sharing a token ID. Recommended, skippable.</Text>
80
+ <Text color={theme.textSubtle}>The token ID + network already make the agent restorable; ENS only adds a public name.</Text>
81
+ </Box>
82
+ <Box marginTop={1}>
83
+ <Select<'ens' | 'skip'>
84
+ options={[
85
+ { value: 'ens', role: 'section', label: 'Set Up Now' },
86
+ { value: 'ens', label: 'Set Up ENS Name', hint: 'Walks you through Root, Name, Review, and Apply' },
87
+ { value: 'skip', role: 'section', label: 'Skip' },
88
+ { value: 'skip', label: 'Skip For Now', hint: 'Continue to model setup. ENS can be added from Identity Hub later.', role: 'utility' },
89
+ ]}
90
+ hintLayout="inline"
91
+ onSubmit={choice => {
92
+ if (choice === 'skip') {
93
+ finishFirstRunIdentity()
94
+ return
95
+ }
96
+ setStep({
97
+ kind: 'edit-profile-ens',
98
+ identity: step.identity,
99
+ registry: step.registry,
100
+ returnTo: { kind: 'first-run-ens-prompt', identity: step.identity, registry: step.registry },
101
+ })
102
+ }}
103
+ onCancel={finishFirstRunIdentity}
104
+ />
105
+ </Box>
106
+ </Surface>
107
+ )
108
+ }
109
+
110
+ if (step.kind === 'menu') {
111
+ return (
112
+ <MenuScreen
113
+ mode={mode}
114
+ config={config}
115
+ identity={identity}
116
+ workingStatus={workingStatus}
117
+ canRebackup={canRebackup}
118
+ reconciliation={reconciliation}
119
+ footer={footer}
120
+ onCreate={() => {
121
+ if (identity) setStep({ kind: 'replace-confirm', next: 'create' })
122
+ else setStep({ kind: 'create-name' })
123
+ }}
124
+ onLoad={() => {
125
+ setCopyNotice(null)
126
+ setStep({ kind: 'restore-wallet', purpose: identity ? 'switch' : 'restore' })
127
+ }}
128
+ onBackupNow={() => setStep({ kind: 'rebackup-confirm', back: { kind: 'menu' } })}
129
+ onRefetchLatest={() => setStep({ kind: 'recovery-refetch-confirm', back: { kind: 'menu' } })}
130
+ onPublicProfile={() => setStep({ kind: 'continuity-public' })}
131
+ onEnsName={() => {
132
+ if (!identity) return
133
+ openEnsEdit({ kind: 'menu' })
134
+ }}
135
+ onWalletSetup={() => {
136
+ if (!identity) return
137
+ const reg = resolveRegistryForIdentity(identity)
138
+ if (!reg) {
139
+ handleStepError(new Error('no agent registry configured for this identity'), { kind: 'menu' })
140
+ return
141
+ }
142
+ setStep({ kind: 'custody-model', identity, registry: reg, returnTo: { kind: 'menu' } })
143
+ }}
144
+ onContinuity={() => setStep({ kind: 'continuity-private' })}
145
+ onIdentityValues={() => setStep({ kind: 'details' })}
146
+ onPrepareTransfer={openTokenTransferFlow}
147
+ onStorage={() => setStep({ kind: 'storage-credential' })}
148
+ onSkip={() => onComplete({ kind: 'skip' })}
149
+ onCancel={closeHub}
150
+ />
151
+ )
152
+ }
153
+
154
+ if (isCreateStep(step)) {
155
+ return (
156
+ <CreateFlow
157
+ step={step}
158
+ walletSession={walletSession}
159
+ onSetStep={setStep}
160
+ onNameSubmit={name => setStep({ kind: 'create-description', name })}
161
+ onDescriptionSubmit={(name, description) => setStep({ kind: 'create-network', name, description })}
162
+ onCustodySubmit={(custodyMode) => {
163
+ if (step.kind !== 'create-custody') return
164
+ setStep({ kind: 'create-preflight', name: step.name, description: step.description, ...(step.network ? { network: step.network } : {}), custodyMode })
165
+ }}
166
+ onRegistrySubmit={async value => {
167
+ if (step.kind !== 'create-registry') return
168
+ try {
169
+ await runRegistrySubmit(value, step, config, onConfigChange, callbacks)
170
+ } catch (err: unknown) {
171
+ setStep({ kind: 'create-registry', name: step.name, description: step.description, resolution: step.resolution, custodyMode: step.custodyMode, error: (err as Error).message })
172
+ }
173
+ }}
174
+ onStorageSubmit={async input => {
175
+ if (step.kind !== 'create-storage') return
176
+ try {
177
+ await runStorageSubmit(input, step, callbacks)
178
+ } catch (err: unknown) {
179
+ setStep({
180
+ kind: 'create-storage',
181
+ name: step.name,
182
+ description: step.description,
183
+ registry: step.registry,
184
+ custodyMode: step.custodyMode,
185
+ error: (err as Error).message,
186
+ pinataJwt: step.pinataJwt,
187
+ })
188
+ }
189
+ }}
190
+ onBack={back}
191
+ onMenu={() => setStep({ kind: 'menu' })}
192
+ />
193
+ )
194
+ }
195
+
196
+ if (step.kind === 'create-network') {
197
+ return (
198
+ <NetworkScreen
199
+ subtitle="Choose where to create this agent."
200
+ footer={footer}
201
+ onSelect={(network: SelectableNetwork) => {
202
+ setStep({ kind: 'create-custody', name: step.name, description: step.description, network })
203
+ }}
204
+ onCancel={back}
205
+ />
206
+ )
207
+ }
208
+
209
+ if (step.kind === 'restore-network') {
210
+ return (
211
+ <NetworkScreen
212
+ subtitle="Choose a network to search for your agents."
213
+ footer={footer}
214
+ onSelect={(network: SelectableNetwork) => {
215
+ try {
216
+ const registry = erc8004ConfigForSupportedChain(chainIdForNetwork(network))
217
+ setStep({ kind: 'restore-discovering', ownerHandle: step.ownerHandle, registry, purpose: step.purpose })
218
+ } catch (err: unknown) {
219
+ handleStepError(err, { kind: 'restore-network', ownerHandle: step.ownerHandle, purpose: step.purpose })
220
+ }
221
+ }}
222
+ onCancel={back}
223
+ />
224
+ )
225
+ }
226
+
227
+ if (isRestoreStep(step)) {
228
+ return (
229
+ <RestoreFlow
230
+ step={step}
231
+ config={config}
232
+ walletSession={walletSession}
233
+ restoreProgress={restoreProgress}
234
+ onRestoreRegistrySubmit={async value => {
235
+ if (step.kind !== 'restore-registry') return
236
+ try {
237
+ await runRestoreRegistrySubmit(value, step, config, onConfigChange, callbacks)
238
+ } catch (err: unknown) {
239
+ setStep({ kind: 'restore-registry', ownerHandle: step.ownerHandle, error: (err as Error).message, purpose: step.purpose })
240
+ }
241
+ }}
242
+ onRetryDiscovery={() => {
243
+ if (step.kind !== 'restore-not-found') return
244
+ setStep({
245
+ kind: 'restore-discovering',
246
+ ownerHandle: step.ownerHandle,
247
+ registry: step.registry,
248
+ purpose: step.purpose,
249
+ })
250
+ }}
251
+ onTokenSelect={value => {
252
+ if (step.kind !== 'restore-select-token') return
253
+ const candidate = step.candidates.find(item => item.agentId.toString() === value)
254
+ if (!candidate?.backup?.cid) return
255
+ setStep({
256
+ kind: 'restore-fetching',
257
+ cid: candidate.backup.cid,
258
+ apiUrl: DEFAULT_IPFS_API_URL,
259
+ candidate,
260
+ requesterAddress: step.requesterAddress,
261
+ purpose: step.purpose,
262
+ })
263
+ }}
264
+ onEnsSubmit={async value => {
265
+ if (step.kind !== 'restore-ens-input') return
266
+ setStep({ ...step, busy: true, error: undefined })
267
+ const resolution = await resolveAgentEnsToCandidate(value, step.registry)
268
+ if (!resolution.ok) {
269
+ setStep({ ...step, busy: false, error: resolution.message })
270
+ return
271
+ }
272
+ if (!resolution.candidate.backup?.cid) {
273
+ setStep({ ...step, busy: false, error: 'This token has no encrypted snapshot. Save the agent first from the owner wallet.' })
274
+ return
275
+ }
276
+ if (!isAddress(step.ownerHandle, { strict: false }) || !canRestoreCandidate(resolution.candidate, getAddress(step.ownerHandle))) {
277
+ setStep({ ...step, busy: false, error: `${shortAddress(step.ownerHandle)} is not a operator wallet for this agent. Sign in with an approved operator wallet, or with the owner wallet that holds the token.` })
278
+ return
279
+ }
280
+ setStep({
281
+ kind: 'restore-fetching',
282
+ cid: resolution.candidate.backup.cid,
283
+ apiUrl: DEFAULT_IPFS_API_URL,
284
+ candidate: resolution.candidate,
285
+ requesterAddress: step.ownerHandle,
286
+ purpose: step.purpose,
287
+ })
288
+ }}
289
+ onTokenIdSubmit={async value => {
290
+ if (step.kind !== 'restore-token-id-input') return
291
+ setStep({ ...step, busy: true, error: undefined })
292
+ const resolution = await resolveAgentTokenIdToCandidate(value, step.registry)
293
+ if (!resolution.ok) {
294
+ setStep({ ...step, busy: false, error: resolution.message })
295
+ return
296
+ }
297
+ if (!resolution.candidate.backup?.cid) {
298
+ setStep({ ...step, busy: false, error: 'This token has no encrypted snapshot. Save the agent first from the owner wallet.' })
299
+ return
300
+ }
301
+ if (!isAddress(step.ownerHandle, { strict: false }) || !canRestoreCandidate(resolution.candidate, getAddress(step.ownerHandle))) {
302
+ setStep({ ...step, busy: false, error: `${shortAddress(step.ownerHandle)} is not a operator wallet for this agent. Sign in with an approved operator wallet, or with the owner wallet that holds the token.` })
303
+ return
304
+ }
305
+ setStep({
306
+ kind: 'restore-fetching',
307
+ cid: resolution.candidate.backup.cid,
308
+ apiUrl: DEFAULT_IPFS_API_URL,
309
+ candidate: resolution.candidate,
310
+ requesterAddress: step.ownerHandle,
311
+ purpose: step.purpose,
312
+ })
313
+ }}
314
+ onPickRecoveryMethod={choice => {
315
+ if (step.kind !== 'restore-recovery-input' && step.kind !== 'restore-select-token') return
316
+ if (choice === 'ens') {
317
+ setStep({ kind: 'restore-ens-input', ownerHandle: step.ownerHandle, registry: step.registry, purpose: step.purpose })
318
+ } else {
319
+ setStep({ kind: 'restore-token-id-input', ownerHandle: step.ownerHandle, registry: step.registry, purpose: step.purpose })
320
+ }
321
+ }}
322
+ onBack={back}
323
+ />
324
+ )
325
+ }
326
+
327
+ if (step.kind === 'details') {
328
+ return (
329
+ <DetailsScreen
330
+ identity={identity}
331
+ config={config}
332
+ copyNotice={copyNotice}
333
+ unlinked={reconciliation?.token === 'unlinked'}
334
+ {...(reconciliation?.onChainOwner ? { onchainOwner: reconciliation.onChainOwner } : {})}
335
+ footer={footer}
336
+ onCopy={async (label, value) => {
337
+ const result = await copyToClipboard(value)
338
+ setCopyNotice(result.ok ? `${label} copied to clipboard.` : `copy failed: ${result.error}`)
339
+ setStep({ kind: 'details' })
340
+ }}
341
+ onBack={back}
342
+ />
343
+ )
344
+ }
345
+
346
+ if (isTokenTransferStep(step)) {
347
+ return (
348
+ <IdentityHubTokenTransferFlow
349
+ step={step}
350
+ callbacks={callbacks}
351
+ footer={footer}
352
+ progress={tokenTransferProgress}
353
+ walletSession={walletSession}
354
+ onSetStep={setStep}
355
+ onBack={back}
356
+ />
357
+ )
358
+ }
359
+
360
+ return <IdentityHubOperationalRoutes controller={controller} footer={footer} />
361
+ }
@@ -0,0 +1,45 @@
1
+ import { getAddress, type Address, type PublicClient } from 'viem'
2
+ import { validateAgentEnsLink, type DiscoverOptions, type EnsValidation } from '../ens/ensLookup.js'
3
+ import {
4
+ validateErc8004TokenOwner,
5
+ type Erc8004RegistryConfig,
6
+ } from '../registry/erc8004.js'
7
+
8
+ type TokenOwnerReadClient = Pick<PublicClient, 'readContract'>
9
+
10
+ type AdvancedEnsRelationshipArgs = {
11
+ fullName: string
12
+ ownerAddress: Address
13
+ registry: Erc8004RegistryConfig
14
+ agentId: string | bigint | undefined
15
+ ensOptions?: DiscoverOptions
16
+ tokenPublicClient?: TokenOwnerReadClient
17
+ }
18
+
19
+ export async function validateAdvancedEnsRelationship(
20
+ args: AdvancedEnsRelationshipArgs,
21
+ ): Promise<EnsValidation> {
22
+ if (args.agentId === undefined || args.agentId === '') {
23
+ return {
24
+ ok: false,
25
+ reason: 'token-owner-lookup-failed',
26
+ detail: 'identity is missing an ERC-8004 token id',
27
+ }
28
+ }
29
+ const agentId = typeof args.agentId === 'bigint' ? args.agentId : BigInt(args.agentId)
30
+ const ownerAddress = getAddress(args.ownerAddress)
31
+ const tokenOwner = await validateErc8004TokenOwner({
32
+ ...args.registry,
33
+ agentId,
34
+ expectedOwner: ownerAddress,
35
+ publicClient: args.tokenPublicClient,
36
+ })
37
+ if (!tokenOwner.ok) {
38
+ return {
39
+ ok: false,
40
+ reason: tokenOwner.reason,
41
+ detail: tokenOwner.detail,
42
+ }
43
+ }
44
+ return validateAgentEnsLink(args.fullName, ownerAddress, args.ensOptions ?? {})
45
+ }
@@ -1,9 +1,10 @@
1
1
  import React from 'react'
2
- import { Box } from 'ink'
2
+ import { Box, Text } from 'ink'
3
+ import { theme } from '../../../ui/theme.js'
3
4
  import { Surface } from '../../../ui/Surface.js'
4
5
  import { Select, type SelectOption } from '../../../ui/Select.js'
5
6
  import type { EthagentConfig, EthagentIdentity } from '../../../storage/config.js'
6
- import { copyableIdentityFields } from '../identityHubModel.js'
7
+ import { copyableIdentityFields, identityValuesCopyHint } from '../model/copy.js'
7
8
  import { IdentitySummary } from './IdentitySummary.js'
8
9
 
9
10
  type CopyAction = `copy:${string}` | 'back'
@@ -12,6 +13,8 @@ type DetailsScreenProps = {
12
13
  identity?: EthagentIdentity
13
14
  config?: EthagentConfig
14
15
  copyNotice?: string | null
16
+ unlinked?: boolean
17
+ onchainOwner?: string
15
18
  footer: React.ReactNode
16
19
  onCopy: (label: string, value: string) => void
17
20
  onBack: () => void
@@ -21,26 +24,29 @@ export const DetailsScreen: React.FC<DetailsScreenProps> = ({
21
24
  identity,
22
25
  config,
23
26
  copyNotice,
27
+ unlinked,
28
+ onchainOwner,
24
29
  footer,
25
30
  onCopy,
26
31
  onBack,
27
32
  }) => {
28
- const copyable = copyableIdentityFields(identity)
33
+ const copyable = copyableIdentityFields(identity, config)
29
34
  const options: Array<SelectOption<CopyAction>> = [
30
- ...(copyable.length > 0 ? [{ value: 'back' as const, role: 'section' as const, prefix: '--', label: 'Values' }] : []),
35
+ ...(copyable.length > 0 ? [{ value: 'back' as const, role: 'section' as const, label: 'Values' }] : []),
31
36
  ...copyable.map(field => ({
32
37
  value: `copy:${field.label}` as const,
33
38
  label: field.label,
34
39
  hint: shortPreview(field.value),
35
40
  })),
36
41
  ...(copyable.length === 0 ? [{ value: 'back' as const, role: 'notice' as const, label: 'No Values Available Yet' }] : []),
37
- { value: 'back', role: 'section', prefix: '--', label: 'Navigation' },
38
- { value: 'back', label: 'Back To Identity Hub', hint: 'Return without copying', role: 'utility' },
42
+ { value: 'back', role: 'section', label: 'Navigation' },
43
+ { value: 'back', label: 'Back', hint: 'Return to Identity Hub menu', role: 'utility' },
39
44
  ]
40
45
 
41
46
  return (
42
- <Surface title="Copy Identity Values" subtitle={copyNotice ?? 'Choose one value to copy.'} footer={footer}>
43
- <IdentitySummary identity={identity} config={config} compact />
47
+ <Surface title="Token Values" subtitle={unlinked ? 'Token no longer linked to this wallet, values retained for reference.' : `${identityValuesCopyHint(identity)}.`} footer={footer}>
48
+ <IdentitySummary identity={identity} config={config} onchainOwner={onchainOwner} />
49
+ {copyNotice ? <Text color={theme.accentPeriwinkle} bold>{copyNotice}</Text> : null}
44
50
  <Box marginTop={1}>
45
51
  <Select<CopyAction>
46
52
  options={options}
@@ -3,26 +3,36 @@ import { Text } from 'ink'
3
3
  import { Surface } from '../../../ui/Surface.js'
4
4
  import { Select } from '../../../ui/Select.js'
5
5
  import { theme } from '../../../ui/theme.js'
6
- import type { IdentityHubErrorView } from '../identityHubModel.js'
6
+ import type { IdentityHubErrorView } from '../model/errors.js'
7
7
  import type { Step } from '../identityHubReducer.js'
8
8
 
9
9
  type ErrorScreenProps = {
10
10
  error: IdentityHubErrorView
11
11
  back: Step
12
12
  footer: React.ReactNode
13
+ closeLabel?: string
14
+ closeHint?: string
13
15
  onBack: (back: Step) => void
14
16
  onClose: () => void
15
17
  }
16
18
 
17
- export const ErrorScreen: React.FC<ErrorScreenProps> = ({ error, back, footer, onBack, onClose }) => (
19
+ export const ErrorScreen: React.FC<ErrorScreenProps> = ({
20
+ error,
21
+ back,
22
+ footer,
23
+ closeLabel = 'Close Identity Hub',
24
+ closeHint = 'Return to chat without retrying',
25
+ onBack,
26
+ onClose,
27
+ }) => (
18
28
  <Surface title={error.title} tone="error" subtitle={error.detail} footer={footer}>
19
29
  {error.hint ? <Text color={theme.dim}>{error.hint}</Text> : null}
20
30
  <Select<'back' | 'close'>
21
31
  options={[
22
- { value: 'back', role: 'section', prefix: '--', label: 'Recovery' },
32
+ { value: 'back', role: 'section', label: 'Recovery' },
23
33
  { value: 'back', label: 'Go Back', hint: 'Return to the previous identity step' },
24
- { value: 'close', role: 'section', prefix: '--', label: 'Exit' },
25
- { value: 'close', label: 'Close Identity Hub', hint: 'Return to chat without retrying', role: 'utility' },
34
+ { value: 'close', role: 'section', label: 'Exit' },
35
+ { value: 'close', label: closeLabel, hint: closeHint, role: 'utility' },
26
36
  ]}
27
37
  hintLayout="inline"
28
38
  onSubmit={choice => {
@@ -0,0 +1,27 @@
1
+ import React from 'react'
2
+ import { Text } from 'ink'
3
+ import { theme } from '../../../ui/theme.js'
4
+
5
+ type FlowTimelineProps = {
6
+ steps: string[]
7
+ current: number
8
+ }
9
+
10
+ export const FlowTimeline: React.FC<FlowTimelineProps> = ({ steps, current }) => (
11
+ <Text>
12
+ {steps.map((step, index) => {
13
+ const n = index + 1
14
+ const active = n === current
15
+ const done = n < current
16
+ const glyph = done ? '✓' : active ? '●' : '○'
17
+ return (
18
+ <React.Fragment key={`${index}:${step}`}>
19
+ {index > 0 ? <Text> </Text> : null}
20
+ <Text color={active ? theme.accentPeriwinkle : done ? theme.dim : theme.textSubtle} bold={active}>
21
+ {glyph} {step}
22
+ </Text>
23
+ </React.Fragment>
24
+ )
25
+ })}
26
+ </Text>
27
+ )