ethagent 1.1.2 → 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 (268) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +126 -30
  3. package/package.json +7 -2
  4. package/src/app/FirstRun.tsx +190 -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 +35 -15
  11. package/src/chat/ContextLimitView.tsx +4 -4
  12. package/src/chat/ContinuityEditReviewView.tsx +10 -22
  13. package/src/chat/CopyPicker.tsx +0 -1
  14. package/src/chat/MessageList.tsx +62 -45
  15. package/src/chat/PermissionPrompt.tsx +13 -9
  16. package/src/chat/PlanApprovalView.tsx +3 -3
  17. package/src/chat/ResumeView.tsx +1 -4
  18. package/src/chat/RewindView.tsx +2 -2
  19. package/src/chat/chatInputState.ts +1 -1
  20. package/src/chat/chatScreenUtils.ts +22 -11
  21. package/src/chat/chatSessionState.ts +2 -2
  22. package/src/chat/chatTurnOrchestrator.ts +16 -81
  23. package/src/chat/commands.ts +1 -1
  24. package/src/chat/textCursor.ts +1 -1
  25. package/src/chat/transcriptViewport.ts +2 -7
  26. package/src/cli/ResetConfirmView.tsx +1 -1
  27. package/src/cli/main.tsx +9 -3
  28. package/src/cli/preview.tsx +0 -5
  29. package/src/cli/updateNotice.ts +4 -2
  30. package/src/identity/continuity/editor.ts +7 -107
  31. package/src/identity/continuity/envelope.ts +1048 -40
  32. package/src/identity/continuity/history.ts +4 -4
  33. package/src/identity/continuity/localBackup.ts +249 -0
  34. package/src/identity/continuity/privateEdit/apply.ts +170 -0
  35. package/src/identity/continuity/privateEdit/diff.ts +82 -0
  36. package/src/identity/continuity/privateEdit/files.ts +23 -0
  37. package/src/identity/continuity/privateEdit/types.ts +28 -0
  38. package/src/identity/continuity/privateEdit.ts +10 -298
  39. package/src/identity/continuity/publicSkills.ts +8 -9
  40. package/src/identity/continuity/snapshots.ts +17 -6
  41. package/src/identity/continuity/storage/defaults.ts +111 -0
  42. package/src/identity/continuity/storage/files.ts +72 -0
  43. package/src/identity/continuity/storage/markdown.ts +81 -0
  44. package/src/identity/continuity/storage/paths.ts +24 -0
  45. package/src/identity/continuity/storage/scaffold.ts +124 -0
  46. package/src/identity/continuity/storage/status.ts +86 -0
  47. package/src/identity/continuity/storage/types.ts +27 -0
  48. package/src/identity/continuity/storage.ts +32 -507
  49. package/src/identity/continuity/zipWriter.ts +95 -0
  50. package/src/identity/crypto/backupEnvelope.ts +14 -247
  51. package/src/identity/crypto/eth.ts +7 -7
  52. package/src/identity/ens/agentRecords.ts +96 -0
  53. package/src/identity/ens/ensAutomation/contracts.ts +38 -0
  54. package/src/identity/ens/ensAutomation/delete.ts +80 -0
  55. package/src/identity/ens/ensAutomation/names.ts +14 -0
  56. package/src/identity/ens/ensAutomation/operators.ts +29 -0
  57. package/src/identity/ens/ensAutomation/read.ts +114 -0
  58. package/src/identity/ens/ensAutomation/root.ts +63 -0
  59. package/src/identity/ens/ensAutomation/setup.ts +284 -0
  60. package/src/identity/ens/ensAutomation/transactions.ts +107 -0
  61. package/src/identity/ens/ensAutomation/types.ts +126 -0
  62. package/src/identity/ens/ensAutomation.ts +29 -0
  63. package/src/identity/ens/ensLookup/client.ts +43 -0
  64. package/src/identity/ens/ensLookup/constants.ts +26 -0
  65. package/src/identity/ens/ensLookup/discovery.ts +70 -0
  66. package/src/identity/ens/ensLookup/names.ts +34 -0
  67. package/src/identity/ens/ensLookup/records.ts +45 -0
  68. package/src/identity/ens/ensLookup/resolve.ts +75 -0
  69. package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
  70. package/src/identity/ens/ensLookup/types.ts +38 -0
  71. package/src/identity/ens/ensLookup/validation.ts +72 -0
  72. package/src/identity/ens/ensLookup.ts +19 -0
  73. package/src/identity/ens/ensRegistration.ts +199 -0
  74. package/src/identity/ens/resolverDelegation.ts +48 -0
  75. package/src/identity/hub/IdentityHub.tsx +13 -817
  76. package/src/identity/hub/OperationalRoutes.tsx +370 -0
  77. package/src/identity/hub/Routes.tsx +361 -0
  78. package/src/identity/hub/advancedEnsValidation.ts +45 -0
  79. package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
  80. package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
  81. package/src/identity/hub/components/FlowTimeline.tsx +27 -0
  82. package/src/identity/hub/components/IdentitySummary.tsx +190 -0
  83. package/src/identity/hub/components/MenuScreen.tsx +237 -0
  84. package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
  85. package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
  86. package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
  87. package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
  88. package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
  89. package/src/identity/hub/effects/create.ts +310 -0
  90. package/src/identity/hub/effects/ens/flows.ts +218 -0
  91. package/src/identity/hub/effects/ens/index.ts +11 -0
  92. package/src/identity/hub/effects/ens/transactions.ts +239 -0
  93. package/src/identity/hub/effects/index.ts +74 -0
  94. package/src/identity/hub/effects/profile/profileState.ts +173 -0
  95. package/src/identity/hub/effects/publicProfile/index.ts +5 -0
  96. package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
  97. package/src/identity/hub/effects/rebackup/index.ts +7 -0
  98. package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
  99. package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
  100. package/src/identity/hub/effects/receipts.ts +46 -0
  101. package/src/identity/hub/effects/restore/apply.ts +112 -0
  102. package/src/identity/hub/effects/restore/auth.ts +159 -0
  103. package/src/identity/hub/effects/restore/discover.ts +86 -0
  104. package/src/identity/hub/effects/restore/envelopes.ts +21 -0
  105. package/src/identity/hub/effects/restore/fetch.ts +25 -0
  106. package/src/identity/hub/effects/restore/index.ts +22 -0
  107. package/src/identity/hub/effects/restore/recovery.ts +135 -0
  108. package/src/identity/hub/effects/restore/resolve.ts +102 -0
  109. package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
  110. package/src/identity/hub/effects/restore/shared.ts +91 -0
  111. package/src/identity/hub/effects/restoreAdmin.ts +93 -0
  112. package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
  113. package/src/identity/hub/effects/shared/snapshot.ts +336 -0
  114. package/src/identity/hub/effects/shared/sync.ts +190 -0
  115. package/src/identity/hub/effects/token-transfer/index.ts +6 -0
  116. package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
  117. package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
  118. package/src/identity/hub/effects/types.ts +53 -0
  119. package/src/identity/hub/effects/vault/preflight.ts +50 -0
  120. package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
  121. package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
  122. package/src/identity/hub/{screens → flows/continuity}/RecoveryConfirmScreen.tsx +28 -19
  123. package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
  124. package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
  125. package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
  126. package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
  127. package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
  128. package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
  129. package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
  130. package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
  131. package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
  132. package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
  133. package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
  134. package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
  135. package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
  136. package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
  137. package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
  138. package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
  139. package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
  140. package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
  141. package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
  142. package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
  143. package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
  144. package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
  145. package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
  146. package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
  147. package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +23 -44
  148. package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
  149. package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
  150. package/src/identity/hub/identityHubReducer.ts +164 -99
  151. package/src/identity/hub/model/continuity.ts +94 -0
  152. package/src/identity/hub/model/copy.ts +35 -0
  153. package/src/identity/hub/model/custody.ts +54 -0
  154. package/src/identity/hub/model/ens.ts +49 -0
  155. package/src/identity/hub/model/errors.ts +140 -0
  156. package/src/identity/hub/model/format.ts +15 -0
  157. package/src/identity/hub/model/identity.ts +94 -0
  158. package/src/identity/hub/model/network.ts +32 -0
  159. package/src/identity/hub/model/transfer.ts +57 -0
  160. package/src/identity/hub/operatorWallets.ts +131 -0
  161. package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
  162. package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
  163. package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
  164. package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
  165. package/src/identity/hub/reconciliation/index.ts +21 -0
  166. package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
  167. package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
  168. package/src/identity/hub/txGuard.ts +51 -0
  169. package/src/identity/hub/types.ts +17 -0
  170. package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
  171. package/src/identity/hub/useIdentityHubController.ts +396 -0
  172. package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
  173. package/src/identity/hub/utils.ts +79 -0
  174. package/src/identity/identityCompat.ts +34 -0
  175. package/src/identity/profile/agentIcon.ts +61 -0
  176. package/src/identity/profile/imagePicker.ts +12 -12
  177. package/src/identity/registry/erc8004/abi.ts +14 -0
  178. package/src/identity/registry/erc8004/chains.ts +150 -0
  179. package/src/identity/registry/erc8004/client.ts +11 -0
  180. package/src/identity/registry/erc8004/discovery.ts +511 -0
  181. package/src/identity/registry/erc8004/metadata.ts +335 -0
  182. package/src/identity/registry/erc8004/ownership.ts +121 -0
  183. package/src/identity/registry/erc8004/preflight.ts +123 -0
  184. package/src/identity/registry/erc8004/transactions.ts +77 -0
  185. package/src/identity/registry/erc8004/types.ts +88 -0
  186. package/src/identity/registry/erc8004/uri.ts +59 -0
  187. package/src/identity/registry/erc8004/utils.ts +58 -0
  188. package/src/identity/registry/erc8004.ts +53 -1106
  189. package/src/identity/registry/fieldParsers.ts +28 -0
  190. package/src/identity/registry/operatorVault/bytecode.ts +98 -0
  191. package/src/identity/registry/operatorVault/constants.ts +38 -0
  192. package/src/identity/registry/operatorVault/read.ts +246 -0
  193. package/src/identity/registry/operatorVault/transactions.ts +81 -0
  194. package/src/identity/registry/operatorVault.ts +44 -0
  195. package/src/identity/storage/ipfs.ts +26 -24
  196. package/src/identity/wallet/browserWallet/gas.ts +41 -0
  197. package/src/identity/wallet/browserWallet/html.ts +106 -0
  198. package/src/identity/wallet/browserWallet/http.ts +28 -0
  199. package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
  200. package/src/identity/wallet/browserWallet/requests.ts +191 -0
  201. package/src/identity/wallet/browserWallet/session.ts +325 -0
  202. package/src/identity/wallet/browserWallet/types.ts +192 -0
  203. package/src/identity/wallet/browserWallet/validation.ts +74 -0
  204. package/src/identity/wallet/browserWallet.ts +30 -393
  205. package/src/identity/wallet/page/constants.ts +5 -0
  206. package/src/identity/wallet/page/controller.ts +251 -0
  207. package/src/identity/wallet/page/copy.ts +340 -0
  208. package/src/identity/wallet/page/grainient.ts +278 -0
  209. package/src/identity/wallet/page/html.ts +28 -0
  210. package/src/identity/wallet/page/markup.ts +50 -0
  211. package/src/identity/wallet/page/state.ts +9 -0
  212. package/src/identity/wallet/page/styles/base.ts +259 -0
  213. package/src/identity/wallet/page/styles/components.ts +262 -0
  214. package/src/identity/wallet/page/styles/index.ts +5 -0
  215. package/src/identity/wallet/page/styles/responsive.ts +247 -0
  216. package/src/identity/wallet/page/types.ts +47 -0
  217. package/src/identity/wallet/page/view.ts +535 -0
  218. package/src/identity/wallet/page/walletProvider.ts +70 -0
  219. package/src/identity/wallet/page.tsx +38 -0
  220. package/src/identity/wallet/walletPurposeCompat.ts +27 -0
  221. package/src/mcp/manager.ts +0 -1
  222. package/src/models/ModelPicker.tsx +36 -30
  223. package/src/models/catalog.ts +5 -2
  224. package/src/models/huggingface.ts +9 -9
  225. package/src/models/llamacpp.ts +13 -13
  226. package/src/models/modelDisplay.ts +75 -0
  227. package/src/models/modelPickerOptions.ts +16 -3
  228. package/src/models/modelRecommendation.ts +0 -1
  229. package/src/providers/errors.ts +16 -0
  230. package/src/providers/gemini.ts +252 -39
  231. package/src/providers/registry.ts +2 -2
  232. package/src/providers/retry.ts +1 -1
  233. package/src/runtime/sessionMode.ts +1 -1
  234. package/src/runtime/systemPrompt.ts +2 -0
  235. package/src/runtime/toolExecution.ts +18 -22
  236. package/src/runtime/toolIntent.ts +0 -20
  237. package/src/runtime/turn.ts +0 -92
  238. package/src/storage/atomicWrite.ts +4 -1
  239. package/src/storage/config.ts +181 -5
  240. package/src/storage/identity.ts +9 -3
  241. package/src/storage/secrets.ts +2 -2
  242. package/src/tools/bashSafety.ts +8 -0
  243. package/src/tools/changeDirectoryTool.ts +1 -1
  244. package/src/tools/deleteFileTool.ts +4 -4
  245. package/src/tools/editTool.ts +4 -4
  246. package/src/tools/editUtils.ts +5 -5
  247. package/src/tools/privateContinuityEditTool.ts +4 -5
  248. package/src/tools/privateContinuityReadTool.ts +1 -2
  249. package/src/tools/registry.ts +30 -0
  250. package/src/tools/writeFileTool.ts +5 -5
  251. package/src/ui/BrandSplash.tsx +20 -85
  252. package/src/ui/ProgressBar.tsx +3 -5
  253. package/src/ui/Select.tsx +20 -8
  254. package/src/ui/Spinner.tsx +38 -3
  255. package/src/ui/Surface.tsx +2 -2
  256. package/src/ui/TextInput.tsx +63 -20
  257. package/src/ui/theme.ts +7 -34
  258. package/src/utils/openExternal.ts +21 -0
  259. package/src/utils/withRetry.ts +47 -3
  260. package/src/identity/hub/identityHubEffects.ts +0 -937
  261. package/src/identity/hub/identityHubModel.ts +0 -371
  262. package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -156
  263. package/src/identity/hub/screens/EditProfileFlow.tsx +0 -146
  264. package/src/identity/hub/screens/IdentitySummary.tsx +0 -106
  265. package/src/identity/hub/screens/MenuScreen.tsx +0 -117
  266. package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
  267. package/src/identity/wallet/wallet-page/wallet.html +0 -1202
  268. /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
@@ -0,0 +1,117 @@
1
+ import { getAddress, type Address } from 'viem'
2
+ import type { AgentEnsRecords, AgentRecordDiff } from '../../../ens/agentRecords.js'
3
+ import type { EnsRegistryAction, EnsSetupBlockedPlan } from '../../../ens/ensAutomation.js'
4
+ import type { CustodyMode } from '../../model/custody.js'
5
+
6
+ export type EnsLinkOptions = {
7
+ mode: 'simple' | 'advanced'
8
+ ownerAddress?: Address
9
+ operatorWallet?: Address
10
+ }
11
+
12
+ export function recordsDiffHasChanges(recordsDiff: AgentRecordDiff[]): boolean {
13
+ return recordsDiff.some(diff => diff.changed)
14
+ }
15
+
16
+ export function recordsHaveCurrentValues(recordsDiff: AgentRecordDiff[]): boolean {
17
+ return recordsDiff.some(diff => diff.current.trim())
18
+ }
19
+
20
+ export function emptyAgentEnsRecords(): AgentEnsRecords {
21
+ return { token: '', profile: '' }
22
+ }
23
+
24
+ export function unlinkEnsLinkOptions(savedCustodyMode: CustodyMode | undefined, savedOwnerAddress: string): EnsLinkOptions {
25
+ if (savedCustodyMode === 'advanced' && /^0x[0-9a-fA-F]{40}$/.test(savedOwnerAddress)) {
26
+ return { mode: 'advanced', ownerAddress: getAddress(savedOwnerAddress as Address) }
27
+ }
28
+ return { mode: 'simple' }
29
+ }
30
+
31
+ export function discoveryErrorMessage(errors: string[]): string {
32
+ return errors.find(Boolean) ?? 'ENS lookup failed'
33
+ }
34
+
35
+ export function modeSwitchHeading(
36
+ currentEnsName: string,
37
+ currentMode: CustodyMode | undefined,
38
+ nextEnsName: string,
39
+ nextMode: 'simple' | 'advanced',
40
+ ): string {
41
+ if (!currentEnsName && !currentMode) return 'Automation'
42
+ const currentTopology = currentMode === 'advanced' ? 'advanced' : currentMode === 'simple' ? 'simple' : undefined
43
+ const modeChanged = currentTopology !== undefined && currentTopology !== nextMode
44
+ if (modeChanged) return 'Prepare ENS Setup Switch'
45
+ if (currentEnsName !== nextEnsName) return 'Prepare ENS Name Switch'
46
+ return 'Automation'
47
+ }
48
+
49
+ export function setupSwitchNotice(
50
+ currentEnsName: string,
51
+ currentMode: CustodyMode | undefined,
52
+ nextEnsName: string,
53
+ nextMode: 'simple' | 'advanced',
54
+ ): string | null {
55
+ if (!currentEnsName && !currentMode) return null
56
+ const currentTopology = currentMode === 'advanced' ? 'advanced' : currentMode === 'simple' ? 'simple' : undefined
57
+ if (currentEnsName === nextEnsName && currentTopology === nextMode) return null
58
+ return 'This replaces the saved ENS setup directly. Reset is only for clearing the current link.'
59
+ }
60
+
61
+ export function advancedSubdomainStatusText(action: EnsRegistryAction): string {
62
+ switch (action) {
63
+ case 'create-subdomain':
64
+ case 'create-wrapped-subdomain':
65
+ return 'Subdomain check: the owner wallet can create this agent ENS name during setup.'
66
+ case 'set-resolver':
67
+ case 'set-wrapped-resolver':
68
+ return 'Subdomain check: the owner wallet can prepare this ENS name during setup.'
69
+ case 'none':
70
+ return 'Subdomain check: this ENS name is ready.'
71
+ }
72
+ }
73
+
74
+ export function networkLabelForChainId(chainId: number): string {
75
+ switch (chainId) {
76
+ case 1: return 'Ethereum Mainnet'
77
+ case 8453: return 'Base'
78
+ default: return `Chain ${chainId}`
79
+ }
80
+ }
81
+
82
+ export function manualReasonTitle(reason: EnsSetupBlockedPlan['reason']): string {
83
+ switch (reason) {
84
+ case 'wrapped-parent':
85
+ case 'subdomain-wrapped':
86
+ return 'ENS NameWrapper ownership could not be verified'
87
+ case 'token-record-collision':
88
+ return 'Subdomain already points to another token'
89
+ case 'token-owner-mismatch':
90
+ return 'Owner wallet does not own this ERC-8004 token'
91
+ case 'token-owner-lookup-failed':
92
+ return 'Could not verify ERC-8004 token owner'
93
+ case 'parent-missing-resolver':
94
+ return 'Parent ENS name needs a resolver'
95
+ case 'subdomain-owned-by-other':
96
+ return 'Subdomain is managed by another wallet'
97
+ case 'operator-matches-owner':
98
+ return 'Operator wallet must be separate'
99
+ case 'root-not-owned':
100
+ return 'Parent ENS name is not manageable'
101
+ case 'root-owner-mismatch':
102
+ return 'Connected wallet does not manage the parent ENS name'
103
+ case 'invalid-root':
104
+ case 'invalid-label':
105
+ case 'missing-token-id':
106
+ case 'lookup-failed':
107
+ return 'ENS setup needs attention'
108
+ }
109
+ }
110
+
111
+ export function readValidationFromState(state: Record<string, unknown> | undefined): { ok: boolean; reason?: string } | null {
112
+ const raw = state?.ensValidation
113
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null
114
+ const obj = raw as Record<string, unknown>
115
+ if (typeof obj.ok !== 'boolean') return null
116
+ return { ok: obj.ok, ...(typeof obj.reason === 'string' ? { reason: obj.reason } : {}) }
117
+ }
@@ -0,0 +1,91 @@
1
+ import type { Address } from 'viem'
2
+ import type { EthagentIdentity } from '../../../../storage/config.js'
3
+ import type {
4
+ AgentEnsRecordState,
5
+ AgentEnsRecords,
6
+ AgentRecordDiff,
7
+ } from '../../../ens/agentRecords.js'
8
+ import type { EnsValidation } from '../../../ens/ensLookup.js'
9
+ import type {
10
+ EnsRegistryAction,
11
+ EnsSetupBlockedPlan,
12
+ EnsSetupPlan,
13
+ EnsSubdomainDeletePlan,
14
+ } from '../../../ens/ensAutomation.js'
15
+ import type { Erc8004RegistryConfig } from '../../../registry/erc8004.js'
16
+ import type { EnsLinkOptions } from './ensEditCopy.js'
17
+
18
+ export type EnsIssueValidation = Extract<EnsValidation, { ok: false }>
19
+
20
+ export type RegisterCommitPhase = {
21
+ label: string
22
+ secret: `0x${string}`
23
+ price: { base: bigint; premium: bigint; total: bigint }
24
+ }
25
+
26
+ export type SimpleEnsPhase =
27
+ | { kind: 'discovering' }
28
+ | { kind: 'pick-parent' }
29
+ | { kind: 'manual-parent' }
30
+ | { kind: 'register-root-input'; error?: string }
31
+ | { kind: 'register-root-pricing'; label: string; secret: `0x${string}`; busy: boolean; error?: string; price?: { base: bigint; premium: bigint; total: bigint } }
32
+ | { kind: 'register-root-commit-tx'; label: string; secret: `0x${string}`; price: { base: bigint; premium: bigint; total: bigint } }
33
+ | { kind: 'register-root-wait'; label: string; secret: `0x${string}`; price: { base: bigint; premium: bigint; total: bigint }; commitMinedAt: number }
34
+ | { kind: 'register-root-tx'; label: string; secret: `0x${string}`; price: { base: bigint; premium: bigint; total: bigint } }
35
+ | { kind: 'register-root-done'; fullName: string }
36
+ | { kind: 'pick-subdomain'; parent: string; label?: string; error?: string }
37
+ | { kind: 'simple-name-missing'; fullName: string; validation: EnsIssueValidation }
38
+ | { kind: 'simple-create-preflight'; rootName: string; label: string; fullName: string }
39
+ | { kind: 'simple-create-review'; setup: EnsSetupPlan }
40
+ | { kind: 'simple-create-blocked'; fallback: EnsSetupBlockedPlan }
41
+ | { kind: 'validating'; fullName: string; mode: 'simple' | 'advanced'; ownerAddress?: Address; operatorWallet?: Address }
42
+ | { kind: 'review'; fullName: string; validation: EnsValidation; recordsDiff: AgentRecordDiff[]; currentRecords: AgentEnsRecordState; nextRecords: AgentEnsRecords; mode: 'simple' | 'advanced'; ownerAddress?: Address; operatorWallet?: Address }
43
+
44
+ export type AdvancedEnsPhase =
45
+ | { kind: 'advanced-transfer-check' }
46
+ | { kind: 'advanced-root'; rootName?: string; error?: string }
47
+ | { kind: 'advanced-root-check'; rootName: string }
48
+ | { kind: 'advanced-subdomain'; rootName: string; label?: string; error?: string }
49
+ | { kind: 'advanced-subdomain-check'; rootName: string; label: string }
50
+ | { kind: 'advanced-operator-wallet'; rootName: string; label: string; registryAction?: EnsRegistryAction; error?: string }
51
+ | { kind: 'advanced-operator-wallet-manual'; rootName: string; label: string; error?: string }
52
+ | { kind: 'advanced-operator-wallet-connecting'; rootName: string; label: string }
53
+ | { kind: 'advanced-preflight'; rootName: string; label: string; operatorWallet: Address }
54
+ | { kind: 'advanced-review'; setup: EnsSetupPlan }
55
+ | { kind: 'advanced-manual'; fallback: EnsSetupBlockedPlan }
56
+
57
+ export type UnlinkEnsPhase =
58
+ | { kind: 'unlink-loading'; fullName: string }
59
+ | { kind: 'unlink-review'; fullName: string; currentRecords: AgentEnsRecordState; recordsDiff: AgentRecordDiff[] }
60
+
61
+ export type DeleteSubdomainPhase =
62
+ | { kind: 'delete-subdomain-preflight'; fullName: string }
63
+ | { kind: 'delete-subdomain-confirm'; plan: EnsSubdomainDeletePlan }
64
+ | { kind: 'delete-subdomain-blocked'; fullName: string; reason: string }
65
+ | { kind: 'delete-subdomain-tx'; plan: EnsSubdomainDeletePlan }
66
+ | { kind: 'delete-subdomain-done'; fullName: string }
67
+
68
+ export type EnsPhase =
69
+ | { kind: 'mode-select' }
70
+ | SimpleEnsPhase
71
+ | AdvancedEnsPhase
72
+ | UnlinkEnsPhase
73
+ | DeleteSubdomainPhase
74
+
75
+ export type DiscoveryState =
76
+ | { status: 'idle' }
77
+ | { status: 'loading' }
78
+ | { status: 'ok'; names: string[]; warning?: string }
79
+ | { status: 'error'; message: string; names: string[] }
80
+
81
+ export type EnsEditProps = {
82
+ identity: EthagentIdentity
83
+ registry: Erc8004RegistryConfig
84
+ onEnsLink: (fullName: string, options: EnsLinkOptions) => void
85
+ onEnsUnlink: () => void
86
+ onEnsRecordsUpdate: (fullName: string, records: AgentEnsRecords, options: EnsLinkOptions, clearRecords?: boolean, currentRecords?: AgentEnsRecordState) => void
87
+ onEnsSetup: (setup: EnsSetupPlan) => void
88
+ onManageOperatorWalletAccess: () => void
89
+ initialView?: 'advanced'
90
+ onBack: () => void
91
+ }
@@ -0,0 +1,271 @@
1
+ import React from 'react'
2
+ import { Box, Text } from 'ink'
3
+ import { Surface } from '../../../../ui/Surface.js'
4
+ import { Select } from '../../../../ui/Select.js'
5
+ import { TextInput } from '../../../../ui/TextInput.js'
6
+ import { theme } from '../../../../ui/theme.js'
7
+ import type { AgentEnsRecordState, AgentEnsRecords } from '../../../ens/agentRecords.js'
8
+ import type { EnsSetupPlan } from '../../../ens/ensAutomation.js'
9
+ import type { Step } from '../../identityHubReducer.js'
10
+ import { readIdentityStateString } from '../../model/custody.js'
11
+ import { FlowTimeline } from '../../components/FlowTimeline.js'
12
+ import { validateAgentIconReference } from '../../../profile/agentIcon.js'
13
+ import { EnsEditFlow, type EnsLinkOptions } from '../ens/EnsEditFlow.js'
14
+
15
+ type EditProfileFlowProps = {
16
+ step: Extract<Step, { kind: 'edit-profile-name' | 'edit-profile-description' | 'edit-profile-image' | 'edit-profile-review' | 'edit-profile-ens' }>
17
+ onNameSubmit: (name: string) => void
18
+ onDescriptionSubmit: (description: string) => void
19
+ onIconSubmit: (iconPath?: string) => void
20
+ onIconPick: () => void
21
+ onReviewSave: () => void
22
+ onEnsLink: (fullName: string, options: EnsLinkOptions) => void
23
+ onEnsUnlink: () => void
24
+ onEnsRecordsUpdate: (fullName: string, records: AgentEnsRecords, options: EnsLinkOptions, clearRecords?: boolean, currentRecords?: AgentEnsRecordState) => void
25
+ onEnsSetup: (setup: EnsSetupPlan) => void
26
+ onManageOperatorWalletAccess: () => void
27
+ onBack: () => void
28
+ onMenu: () => void
29
+ }
30
+
31
+ const footerHint = (hint: string) => <Text color={theme.dim}>{hint}</Text>
32
+ export const EDIT_PROFILE_STEPS = ['Name', 'Description', 'Icon', 'Review', 'Save']
33
+ const EDIT_NEXT_FOOTER = 'enter next · esc back'
34
+ const EDIT_DESCRIPTION_FOOTER = 'enter next · shift+enter newline · esc back'
35
+
36
+ export const EditProfileFlow: React.FC<EditProfileFlowProps> = ({
37
+ step,
38
+ onNameSubmit,
39
+ onDescriptionSubmit,
40
+ onIconSubmit,
41
+ onIconPick,
42
+ onReviewSave,
43
+ onEnsLink,
44
+ onEnsUnlink,
45
+ onEnsRecordsUpdate,
46
+ onEnsSetup,
47
+ onManageOperatorWalletAccess,
48
+ onBack,
49
+ onMenu,
50
+ }) => {
51
+ if (step.kind === 'edit-profile-name') {
52
+ const currentName = step.name ?? readIdentityStateString(step.identity.state, 'name')
53
+ return (
54
+ <Surface
55
+ title="Edit Name, Description, Icon"
56
+ subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={1} />}
57
+ footer={footerHint(EDIT_NEXT_FOOTER)}
58
+ >
59
+ <Text color={theme.dim}>Saved: {readIdentityStateString(step.identity.state, 'name') || '(unnamed)'}</Text>
60
+ <Box marginTop={1}>
61
+ <TextInput
62
+ key="edit-profile-name"
63
+ initialValue={currentName}
64
+ placeholder="agent name"
65
+ validate={value => value.trim().length >= 2 ? null : 'name must be at least 2 characters'}
66
+ onSubmit={value => onNameSubmit(value.trim())}
67
+ onCancel={onMenu}
68
+ />
69
+ </Box>
70
+ </Surface>
71
+ )
72
+ }
73
+
74
+ if (step.kind === 'edit-profile-image') {
75
+ return <AgentIconStep step={step} onIconSubmit={onIconSubmit} onIconPick={onIconPick} onBack={onBack} />
76
+ }
77
+
78
+ if (step.kind === 'edit-profile-review') {
79
+ return (
80
+ <EditProfileReviewStep
81
+ step={step}
82
+ onSave={onReviewSave}
83
+ onBack={onBack}
84
+ />
85
+ )
86
+ }
87
+
88
+ if (step.kind === 'edit-profile-ens') {
89
+ return (
90
+ <EnsEditFlow
91
+ identity={step.identity}
92
+ registry={step.registry}
93
+ onEnsLink={onEnsLink}
94
+ onEnsUnlink={onEnsUnlink}
95
+ onEnsRecordsUpdate={onEnsRecordsUpdate}
96
+ onEnsSetup={onEnsSetup}
97
+ onManageOperatorWalletAccess={onManageOperatorWalletAccess}
98
+ initialView={step.initialView}
99
+ onBack={onBack}
100
+ />
101
+ )
102
+ }
103
+
104
+ const currentDescription = readIdentityStateString(step.identity.state, 'description')
105
+ const draftDescription = step.description ?? currentDescription
106
+ return (
107
+ <Surface
108
+ title="Edit Name, Description, Icon"
109
+ subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={2} />}
110
+ footer={footerHint(EDIT_DESCRIPTION_FOOTER)}
111
+ >
112
+ <Text color={theme.dim}>Saved: {currentDescription || '(no description)'}</Text>
113
+ <Box marginTop={1}>
114
+ <TextInput
115
+ key="edit-profile-description"
116
+ initialValue={draftDescription}
117
+ placeholder="description"
118
+ allowEmpty
119
+ multiline
120
+ onSubmit={value => onDescriptionSubmit(value.trim())}
121
+ onCancel={onBack}
122
+ />
123
+ </Box>
124
+ </Surface>
125
+ )
126
+ }
127
+
128
+ const AgentIconStep: React.FC<{
129
+ step: Extract<Step, { kind: 'edit-profile-image' }>
130
+ onIconSubmit: (iconPath?: string) => void
131
+ onIconPick: () => void
132
+ onBack: () => void
133
+ }> = ({ step, onIconSubmit, onIconPick, onBack }) => {
134
+ const [entryMode, setEntryMode] = React.useState(false)
135
+ const currentIcon = readIdentityStateString(step.identity.state, 'imageUrl')
136
+ const selectedIcon = describeDraftIcon(step.imagePath, currentIcon)
137
+
138
+ if (entryMode) {
139
+ return (
140
+ <Surface
141
+ title="Edit Name, Description, Icon"
142
+ subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={3} />}
143
+ footer={footerHint(EDIT_NEXT_FOOTER)}
144
+ >
145
+ <Text color={theme.dim}>Current: {currentIcon ? shortIconReference(currentIcon) : '(no icon)'}</Text>
146
+ <Box marginTop={1}>
147
+ <TextInput
148
+ key="edit-profile-icon-entry"
149
+ placeholder="https://.../icon.png or C:\\path\\icon.png"
150
+ validate={validateAgentIconReference}
151
+ onSubmit={value => onIconSubmit(value.trim())}
152
+ onCancel={onBack}
153
+ />
154
+ </Box>
155
+ </Surface>
156
+ )
157
+ }
158
+
159
+ return (
160
+ <Surface
161
+ title="Edit Name, Description, Icon"
162
+ subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={3} />}
163
+ footer={footerHint('enter select · esc back')}
164
+ >
165
+ <Box flexDirection="column">
166
+ <Text color={theme.dim}>Agent Icon: {selectedIcon}</Text>
167
+ {step.error ? <Text color={theme.accentError}>{step.error}</Text> : null}
168
+ </Box>
169
+ <Box marginTop={1}>
170
+ <Select<'choose' | 'enter' | 'skip' | 'delete' | 'back'>
171
+ options={[
172
+ { value: 'choose', role: 'section', label: 'Icon Source' },
173
+ { value: 'choose', label: 'Choose Local File', hint: 'Open the operating system file picker' },
174
+ { value: 'enter', label: 'Enter URL Or Path', hint: 'Use https, ipfs, or a local media path' },
175
+ { value: 'skip', role: 'section', label: 'Current Icon' },
176
+ { value: 'skip', label: currentIcon ? 'Keep Current Icon' : 'No Icon', hint: 'Continue without changing the icon' },
177
+ { value: 'delete', label: 'Remove Agent Icon', hint: 'Clear the public profile icon', disabled: !currentIcon },
178
+ { value: 'back', role: 'section', label: 'Navigation' },
179
+ { value: 'back', label: 'Back', hint: 'Return to description', role: 'utility' },
180
+ ]}
181
+ hintLayout="inline"
182
+ onSubmit={choice => {
183
+ if (choice === 'choose') return onIconPick()
184
+ if (choice === 'enter') { setEntryMode(true); return }
185
+ if (choice === 'delete') return onIconSubmit('delete')
186
+ if (choice === 'skip') return onIconSubmit(undefined)
187
+ return onBack()
188
+ }}
189
+ onCancel={onBack}
190
+ />
191
+ </Box>
192
+ </Surface>
193
+ )
194
+ }
195
+
196
+ const EditProfileReviewStep: React.FC<{
197
+ step: Extract<Step, { kind: 'edit-profile-review' }>
198
+ onSave: () => void
199
+ onBack: () => void
200
+ }> = ({ step, onSave, onBack }) => {
201
+ const currentIcon = readIdentityStateString(step.identity.state, 'imageUrl')
202
+ return (
203
+ <Surface
204
+ title="Edit Name, Description, Icon"
205
+ subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={4} />}
206
+ footer={footerHint('enter save · esc back')}
207
+ >
208
+ <Box flexDirection="column">
209
+ <ReviewRow label="Name" value={step.name || '(unnamed)'} />
210
+ <ReviewRow label="Description" value={step.description || '(no description)'} />
211
+ <ReviewRow label="Agent Icon" value={describeDraftIcon(step.imagePath, currentIcon)} />
212
+ </Box>
213
+ <Box marginTop={1}>
214
+ <Select<'save' | 'back'>
215
+ options={[
216
+ { value: 'save', label: 'Save Public Profile', hint: 'Publish public profile and update the token URI' },
217
+ { value: 'back', role: 'section', label: 'Navigation' },
218
+ { value: 'back', label: 'Back', hint: 'Return to Agent Icon', role: 'utility' },
219
+ ]}
220
+ hintLayout="inline"
221
+ onSubmit={choice => choice === 'save' ? onSave() : onBack()}
222
+ onCancel={onBack}
223
+ />
224
+ </Box>
225
+ </Surface>
226
+ )
227
+ }
228
+
229
+ const ReviewRow: React.FC<{ label: string; value: string }> = ({ label, value }) => {
230
+ const lines = value.split('\n')
231
+ return (
232
+ <Box flexDirection="row">
233
+ <Box width={13} flexShrink={0}>
234
+ <Text color={theme.dim}>{label}</Text>
235
+ </Box>
236
+ <Box flexDirection="column" flexGrow={1}>
237
+ {lines.map((line, i) => (
238
+ <Text key={i} color={theme.text}>{line || ' '}</Text>
239
+ ))}
240
+ </Box>
241
+ </Box>
242
+ )
243
+ }
244
+
245
+ function describeDraftIcon(imagePath: string | undefined, currentIcon: string): string {
246
+ if (imagePath === 'delete') return 'Remove current icon'
247
+ if (imagePath) return shortIconReference(imagePath)
248
+ return currentIcon ? shortIconReference(currentIcon) : '(no icon)'
249
+ }
250
+
251
+ function shortIconReference(value: string): string {
252
+ const trimmed = value.trim()
253
+ if (trimmed.length <= 56) return trimmed
254
+ const url = shortUrlReference(trimmed)
255
+ if (url) return url
256
+ return `${trimmed.slice(0, 24)}...${trimmed.slice(-20)}`
257
+ }
258
+
259
+ function shortUrlReference(value: string): string | null {
260
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) return null
261
+ try {
262
+ const url = new URL(value)
263
+ const parts = url.pathname.split('/').filter(Boolean)
264
+ const file = parts.at(-1)
265
+ if (!file) return `${url.protocol}//${url.hostname}`
266
+ return `${url.protocol}//${url.hostname}/.../${file}`
267
+ } catch {
268
+ if (!/^ipfs:\/\//i.test(value)) return null
269
+ return `${value.slice(0, 22)}...${value.slice(-18)}`
270
+ }
271
+ }