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,94 @@
1
+ import type { ContinuityWorkingTreeStatus } from '../../continuity/storage.js'
2
+ import type { EthagentIdentity } from '../../../storage/config.js'
3
+
4
+ export function hasPendingPublish(identity?: EthagentIdentity): boolean {
5
+ if (!identity?.backup?.cid) return false
6
+ if (!identity.metadataCid) return false
7
+ if (!identity.backup.metadataCid) return true
8
+ return identity.backup.metadataCid !== identity.metadataCid
9
+ }
10
+
11
+ export type LocalChangeStatusView = {
12
+ label: string
13
+ detail: string
14
+ tone: 'ok' | 'warn' | 'dim'
15
+ files: string[]
16
+ hasLocalChanges: boolean
17
+ }
18
+
19
+ export function changedContinuitySnapshotFiles(
20
+ workingStatus?: ContinuityWorkingTreeStatus | null,
21
+ ): string[] {
22
+ if (!workingStatus?.localContentHashes || !workingStatus.publishedContentHashes) return []
23
+ const files: Array<keyof typeof workingStatus.localContentHashes> = ['SOUL.md', 'MEMORY.md', 'skills.json']
24
+ return files
25
+ .filter(file => workingStatus.localContentHashes?.[file] !== workingStatus.publishedContentHashes?.[file])
26
+ .map(displayContinuitySnapshotFile)
27
+ }
28
+
29
+ function displayContinuitySnapshotFile(file: keyof NonNullable<ContinuityWorkingTreeStatus['localContentHashes']>): string {
30
+ return file
31
+ }
32
+
33
+ export function localChangeStatusView(
34
+ workingStatus?: ContinuityWorkingTreeStatus | null,
35
+ ): LocalChangeStatusView {
36
+ if (!workingStatus) {
37
+ return {
38
+ label: 'Local Changes',
39
+ detail: '',
40
+ tone: 'dim',
41
+ files: [],
42
+ hasLocalChanges: false,
43
+ }
44
+ }
45
+
46
+ if (workingStatus.publishState === 'published') {
47
+ return {
48
+ label: 'Local Changes',
49
+ detail: 'None detected',
50
+ tone: 'ok',
51
+ files: [],
52
+ hasLocalChanges: false,
53
+ }
54
+ }
55
+
56
+ if (workingStatus.publishState === 'local-changes') {
57
+ const files = changedContinuitySnapshotFiles(workingStatus)
58
+ return {
59
+ label: 'Local Changes',
60
+ detail: files.length > 0 ? `Detected: ${files.join(', ')}` : 'Detected: local files differ from saved snapshot',
61
+ tone: 'warn',
62
+ files,
63
+ hasLocalChanges: true,
64
+ }
65
+ }
66
+
67
+ if (workingStatus.publishState === 'not-published') {
68
+ return {
69
+ label: 'Local Changes',
70
+ detail: 'Snapshot not saved yet',
71
+ tone: 'warn',
72
+ files: [],
73
+ hasLocalChanges: false,
74
+ }
75
+ }
76
+
77
+ if (workingStatus.publishState === 'verify-needed') {
78
+ return {
79
+ label: 'Local Changes',
80
+ detail: 'Unable to verify saved snapshot',
81
+ tone: 'warn',
82
+ files: [],
83
+ hasLocalChanges: false,
84
+ }
85
+ }
86
+
87
+ return {
88
+ label: 'Local Changes',
89
+ detail: 'Local files not restored',
90
+ tone: 'warn',
91
+ files: [],
92
+ hasLocalChanges: false,
93
+ }
94
+ }
@@ -0,0 +1,35 @@
1
+ import type { EthagentConfig, EthagentIdentity } from '../../../storage/config.js'
2
+ import { readCustodyMode, readIdentityStateString } from './custody.js'
3
+
4
+ type CopyableField = {
5
+ label: string
6
+ value: string
7
+ }
8
+
9
+ export function copyableIdentityFields(identity?: EthagentIdentity, config?: EthagentConfig): CopyableField[] {
10
+ if (!identity) return []
11
+ const fields: CopyableField[] = []
12
+ if (identity.agentId) fields.push({ label: 'Token ID', value: identity.agentId })
13
+ const ensName = readIdentityStateString(identity.state, 'ensName')
14
+ if (ensName) fields.push({ label: 'ENS Name', value: ensName })
15
+ const agentUri = identity.agentUri ?? (identity.metadataCid ? `ipfs://${identity.metadataCid}` : undefined)
16
+ if (agentUri) fields.push({ label: 'Agent URI', value: agentUri })
17
+ const ownerAddress = readIdentityStateString(identity.state, 'ownerAddress')
18
+ const owner = identity.ownerAddress ?? identity.address
19
+ const ownerWallet = ownerAddress || owner
20
+ if (ownerWallet) fields.push({ label: 'Owner Wallet', value: ownerWallet })
21
+ const custodyMode = readCustodyMode(identity.state)
22
+ if (custodyMode === 'advanced') {
23
+ const vaultAddress = readIdentityStateString(identity.state, 'operatorVaultAddress')
24
+ if (vaultAddress) fields.push({ label: 'Agent Vault', value: vaultAddress })
25
+ }
26
+ const activeOperator = readIdentityStateString(identity.state, 'activeOperatorAddress')
27
+ if (activeOperator) fields.push({ label: 'Operator Wallet', value: activeOperator })
28
+ return fields
29
+ }
30
+
31
+ export function identityValuesCopyHint(identity?: EthagentIdentity): string {
32
+ return readIdentityStateString(identity?.state, 'ensName')
33
+ ? 'Copy token, ENS, and token URI pointers'
34
+ : 'Copy token and token URI pointers'
35
+ }
@@ -0,0 +1,54 @@
1
+ import type { EthagentIdentity } from '../../../storage/config.js'
2
+ import { readOwnerAddressField } from '../../identityCompat.js'
3
+
4
+ export type CustodyMode = 'simple' | 'advanced'
5
+
6
+ export function readCustodyMode(state: Record<string, unknown> | undefined): CustodyMode | undefined {
7
+ const s = (state ?? {}) as Record<string, unknown>
8
+ const current = s.custodyMode
9
+ if (current === 'simple' || current === 'advanced') return current
10
+ if (current === 'single') return 'simple'
11
+ if (current === 'multi') return 'advanced'
12
+ const legacy = s.ensMode
13
+ if (legacy === 'advanced') return 'advanced'
14
+ if (legacy === 'simple') return 'simple'
15
+ return undefined
16
+ }
17
+
18
+ export function displayCustodyMode(mode: CustodyMode | undefined): string {
19
+ if (mode === 'advanced') return 'Advanced'
20
+ if (mode === 'simple') return 'Simple'
21
+ return 'Not set'
22
+ }
23
+
24
+ export function identityOwnerAddress(identity?: EthagentIdentity, verifiedOnchainOwner?: string): string {
25
+ const onchainOwner = typeof verifiedOnchainOwner === 'string' ? verifiedOnchainOwner.trim() : ''
26
+ if (onchainOwner) return onchainOwner
27
+ if (!identity) return ''
28
+ return readOwnerAddressField(identity.state as Record<string, unknown> | undefined)
29
+ ?? identity.ownerAddress
30
+ ?? identity.address
31
+ ?? ''
32
+ }
33
+
34
+ export type IdentityPerspective = 'owner' | 'operator' | 'unknown'
35
+
36
+ export function identityPerspective(identity?: EthagentIdentity): IdentityPerspective {
37
+ if (!identity) return 'unknown'
38
+ const connected = identity.connectedWallet?.toLowerCase()
39
+ if (!connected) return 'unknown'
40
+ const ownerAddress = (readOwnerAddressField(identity.state as Record<string, unknown> | undefined) ?? identity.ownerAddress ?? identity.address)?.toLowerCase()
41
+ if (ownerAddress && connected === ownerAddress) return 'owner'
42
+ const activeOp = readIdentityStateString(identity.state as Record<string, unknown> | undefined, 'activeOperatorAddress')?.toLowerCase()
43
+ if (activeOp && connected === activeOp) return 'operator'
44
+ const approvedRaw = (identity.state as Record<string, unknown> | undefined)?.approvedOperatorWallets
45
+ const approved = Array.isArray(approvedRaw) ? approvedRaw as Array<{ address?: unknown }> : []
46
+ if (approved.some(r => typeof r?.address === 'string' && r.address.toLowerCase() === connected)) return 'operator'
47
+ return 'unknown'
48
+ }
49
+
50
+ export function readIdentityStateString(state: Record<string, unknown> | undefined, key: string): string {
51
+ if (key === 'ownerAddress') return readOwnerAddressField(state) ?? ''
52
+ const value = state?.[key]
53
+ return typeof value === 'string' ? value.trim() : ''
54
+ }
@@ -0,0 +1,49 @@
1
+ import type { EthagentIdentity } from '../../../storage/config.js'
2
+ import { readIdentityStateString } from './custody.js'
3
+
4
+ export type EnsStatusView =
5
+ | { kind: 'none' }
6
+ | { kind: 'linked'; name: string }
7
+ | { kind: 'issue'; name: string; reason: string }
8
+
9
+ type EnsValidationRecord = {
10
+ ok: boolean
11
+ reason?: string
12
+ resolvedAddress?: string
13
+ checkedAt?: string
14
+ }
15
+
16
+ function readEnsValidation(state: Record<string, unknown> | undefined): EnsValidationRecord | null {
17
+ const raw = state?.ensValidation
18
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null
19
+ const obj = raw as Record<string, unknown>
20
+ if (typeof obj.ok !== 'boolean') return null
21
+ return {
22
+ ok: obj.ok,
23
+ ...(typeof obj.reason === 'string' ? { reason: obj.reason } : {}),
24
+ ...(typeof obj.resolvedAddress === 'string' ? { resolvedAddress: obj.resolvedAddress } : {}),
25
+ ...(typeof obj.checkedAt === 'string' ? { checkedAt: obj.checkedAt } : {}),
26
+ }
27
+ }
28
+
29
+ export function selectEnsStatus(identity: EthagentIdentity | undefined | null): EnsStatusView {
30
+ if (!identity) return { kind: 'none' }
31
+ const name = readIdentityStateString(identity.state, 'ensName')
32
+ if (!name) return { kind: 'none' }
33
+ const validation = readEnsValidation(identity.state)
34
+ if (validation?.ok) return { kind: 'linked', name }
35
+ return { kind: 'issue', name, reason: validation?.reason ?? 'not yet verified' }
36
+ }
37
+
38
+ export function ensValidationReasonText(reason: string | undefined): string {
39
+ switch (reason) {
40
+ case 'no-owner': return 'Name does not exist on ENS'
41
+ case 'no-resolver': return 'Name has no resolver set'
42
+ case 'address-mismatch': return 'ENS name is not resolving to the expected wallet'
43
+ case 'lookup-failed': return 'Could not reach Ethereum mainnet'
44
+ case 'token-owner-mismatch': return 'Token not held by owner wallet'
45
+ case 'token-owner-lookup-failed': return 'Could not verify ERC-8004 token owner'
46
+ case undefined: return 'Not yet verified'
47
+ default: return reason
48
+ }
49
+ }
@@ -0,0 +1,140 @@
1
+ import { ZodError } from 'zod'
2
+ import { RegisterAgentPreflightError } from '../../registry/erc8004.js'
3
+ import { AgentStateOwnerMismatchError } from '../../crypto/backupEnvelope.js'
4
+ import {
5
+ ContinuitySnapshotOwnerMismatchError,
6
+ ContinuityTransferSnapshotTargetMismatchError,
7
+ } from '../../continuity/envelope.js'
8
+ import {
9
+ OperatorVaultBytecodeMismatchError,
10
+ formatOperatorVaultBytecodeMismatchDetail,
11
+ } from '../../registry/operatorVault.js'
12
+ import { BrowserWalletError } from '../../wallet/browserWallet.js'
13
+ import { TxGuardBusyError } from '../txGuard.js'
14
+ import { shortAddress } from './format.js'
15
+
16
+ export type IdentityHubErrorView = {
17
+ title: string
18
+ detail?: string
19
+ hint?: string
20
+ }
21
+
22
+ export function identityHubErrorView(err: unknown): IdentityHubErrorView {
23
+ if (err instanceof ZodError) {
24
+ const issue = err.issues[0]
25
+ const path = issue?.path.join('.') ?? ''
26
+ const message = issue?.message ?? 'Schema validation failed.'
27
+ return {
28
+ title: 'Config Validation Failed',
29
+ detail: path ? `${path}: ${message}` : message,
30
+ hint: 'Internal: a config write was attempted before required fields were set. Restart First Run if this persists.',
31
+ }
32
+ }
33
+ if (err instanceof TxGuardBusyError) {
34
+ return {
35
+ title: 'Already In Progress',
36
+ detail: err.message,
37
+ hint: 'Approve or cancel the open wallet popup, then retry.',
38
+ }
39
+ }
40
+ if (err instanceof RegisterAgentPreflightError) {
41
+ return {
42
+ title: err.title,
43
+ detail: err.detail,
44
+ hint: err.hint,
45
+ }
46
+ }
47
+ if (err instanceof AgentStateOwnerMismatchError) {
48
+ return {
49
+ title: 'Backup Locked to Another Wallet',
50
+ detail: `Wallet ${shortAddress(err.currentOwner)} cannot read state encrypted for ${shortAddress(err.backupOwner)}.`,
51
+ hint: 'Use the wallet that created this backup.',
52
+ }
53
+ }
54
+ if (err instanceof ContinuitySnapshotOwnerMismatchError) {
55
+ return {
56
+ title: 'Snapshot Locked to Previous Wallet',
57
+ detail: 'This token points at an owner-only snapshot. Ask the previous owner to prepare a transfer snapshot before transferring.',
58
+ hint: `Current wallet ${shortAddress(err.currentOwner)} cannot read the snapshot encrypted for ${shortAddress(err.snapshotOwner)}.`,
59
+ }
60
+ }
61
+ if (err instanceof ContinuityTransferSnapshotTargetMismatchError) {
62
+ return {
63
+ title: 'Transfer Snapshot Receiver Mismatch',
64
+ detail: `This transfer snapshot is for receiver ${shortAddress(err.targetOwner)}, but the current token owner is ${shortAddress(err.currentOwner)}.`,
65
+ hint: `Prepare a new transfer snapshot from ${shortAddress(err.snapshotOwner)} to the intended receiver wallet.`,
66
+ }
67
+ }
68
+ if (err instanceof OperatorVaultBytecodeMismatchError) {
69
+ return {
70
+ title: 'Vault Bytecode Mismatch',
71
+ detail: formatOperatorVaultBytecodeMismatchDetail(err),
72
+ hint: 'Open the deploy tx on the block explorer and confirm the input data matches the OperatorVault deploy bytecode plus constructor args before retrying. A persistent mismatch usually means the wallet or RPC substituted the create.',
73
+ }
74
+ }
75
+ if (err instanceof BrowserWalletError) {
76
+ const lines: string[] = []
77
+ if (err.purpose) lines.push(`Action: ${err.purpose}`)
78
+ if (err.method) lines.push(`Method: ${err.method}`)
79
+ if (err.code) lines.push(`Code: ${err.code}`)
80
+ lines.push(`Wallet: ${err.message}`)
81
+ if (err.causes.length) {
82
+ for (const cause of err.causes) lines.push(`Caused by: ${cause}`)
83
+ }
84
+ if (err.data) lines.push(`Data: ${err.data}`)
85
+ return {
86
+ title: err.title ?? 'Wallet Error',
87
+ detail: lines.join('\n'),
88
+ hint: 'Switch to the expected wallet, confirm you have funds, then retry. If the wallet keeps reporting an internal RPC error, change the RPC endpoint in your wallet settings.',
89
+ }
90
+ }
91
+ const message = err instanceof Error ? err.message : String(err)
92
+ if (/^owner wallet required:/i.test(message)) {
93
+ return {
94
+ title: 'Owner Wallet Required',
95
+ detail: capitalizeErrorText(message.replace(/^owner wallet required:\s*/i, '')),
96
+ hint: 'Switch to the owner wallet, then try again.',
97
+ }
98
+ }
99
+ if (/^operator wallet required:/i.test(message)) {
100
+ return {
101
+ title: 'Operator Wallet Required',
102
+ detail: capitalizeErrorText(message.replace(/^operator wallet required:\s*/i, '')),
103
+ hint: 'Switch to the operator wallet, then try again.',
104
+ }
105
+ }
106
+ if (/^Restore Slot Missing:/i.test(message)) {
107
+ return {
108
+ title: 'Wallet Not Included In Snapshot',
109
+ detail: 'ERC-8004 metadata authorizes this wallet, but the latest encrypted snapshot was saved before this wallet had restore access.',
110
+ hint: 'Use the owner wallet once to save a fresh snapshot, then restore with this wallet.',
111
+ }
112
+ }
113
+ if (message === 'fetch failed') {
114
+ return {
115
+ title: 'Storage Unavailable',
116
+ detail: 'Could not reach storage.',
117
+ hint: 'Check the connection, then try again.',
118
+ }
119
+ }
120
+ return {
121
+ title: 'Identity Error',
122
+ detail: capitalizeErrorText(message),
123
+ }
124
+ }
125
+
126
+ export function pinataErrorText(err: unknown): string {
127
+ const view = identityHubErrorView(err)
128
+ return view.detail ?? view.title
129
+ }
130
+
131
+ export function isRegistrationPreflightError(err: unknown): boolean {
132
+ return err instanceof RegisterAgentPreflightError
133
+ }
134
+
135
+ function capitalizeErrorText(value: string): string {
136
+ const trimmed = value.trim()
137
+ if (/^wallet request timed out$/i.test(trimmed)) return 'Wallet Request Timed Out'
138
+ if (!trimmed) return trimmed
139
+ return trimmed.charAt(0).toUpperCase() + trimmed.slice(1)
140
+ }
@@ -0,0 +1,15 @@
1
+ export function shortCid(cid: string): string {
2
+ if (cid.length <= 18) return cid
3
+ return `${cid.slice(0, 10)}...${cid.slice(-6)}`
4
+ }
5
+
6
+ export function shortAddress(address: string): string {
7
+ if (address.length <= 14) return address
8
+ return `${address.slice(0, 6)}...${address.slice(-4)}`
9
+ }
10
+
11
+ export function formatDate(input: string): string {
12
+ const date = new Date(input)
13
+ if (Number.isNaN(date.getTime())) return input
14
+ return date.toISOString().slice(0, 10)
15
+ }
@@ -0,0 +1,94 @@
1
+ import type { EthagentConfig, EthagentIdentity } from '../../../storage/config.js'
2
+ import { supportedErc8004ChainForId, type Erc8004AgentCandidate } from '../../registry/erc8004.js'
3
+ import { readCustodyMode, type CustodyMode } from './custody.js'
4
+ import { formatDate, shortAddress, shortCid } from './format.js'
5
+ import { chainSummaryRow, networkLabel } from './network.js'
6
+
7
+ export const PREFLIGHT_AGENT_URI = 'ipfs://bafybeigdyrztma2dbfczw7q6ooozbxlqzyw5r7w4f3qw2axvvxqg3w6y7q'
8
+
9
+ export function initialAgentState(name: string, description: string, ownerAddress: string): Record<string, unknown> {
10
+ return {
11
+ version: 1,
12
+ name,
13
+ description,
14
+ ownerAddress,
15
+ custodyMode: 'simple' as CustodyMode,
16
+ createdAt: new Date().toISOString(),
17
+ preferences: {},
18
+ memory: {},
19
+ }
20
+ }
21
+
22
+ export function tokenCandidateLabel(candidate: Erc8004AgentCandidate): string {
23
+ return candidate.name?.trim() || `Agent Token #${candidate.agentId.toString()}`
24
+ }
25
+
26
+ export function tokenCandidateSelectLabel(
27
+ candidate: Erc8004AgentCandidate,
28
+ current = false,
29
+ ): string {
30
+ return `${tokenCandidateLabel(candidate)}${current ? ' *' : ''}`
31
+ }
32
+
33
+ export function tokenCandidateHint(candidate: Erc8004AgentCandidate): string {
34
+ const chain = supportedErc8004ChainForId(candidate.chainId)
35
+ const network = chain?.network ? networkLabel(chain.network) : chain?.name ?? `chain ${candidate.chainId}`
36
+ const parts = [
37
+ candidate.name?.trim() ? `token #${candidate.agentId.toString()}` : null,
38
+ network,
39
+ candidate.backup?.createdAt ? `backup ${formatDate(candidate.backup.createdAt)}` : null,
40
+ ].filter((part): part is string => Boolean(part))
41
+ return parts.join(' · ')
42
+ }
43
+
44
+ export function isCurrentAgentCandidate(
45
+ identity: EthagentIdentity | undefined,
46
+ candidate: Erc8004AgentCandidate,
47
+ ): boolean {
48
+ if (!identity?.agentId) return false
49
+ if (identity.agentId !== candidate.agentId.toString()) return false
50
+
51
+ const owner = identity.ownerAddress ?? identity.address
52
+ if (owner && owner.toLowerCase() !== candidate.ownerAddress.toLowerCase()) return false
53
+ if (identity.chainId !== undefined && identity.chainId !== candidate.chainId) return false
54
+ if (
55
+ identity.identityRegistryAddress
56
+ && identity.identityRegistryAddress.toLowerCase() !== candidate.identityRegistryAddress.toLowerCase()
57
+ ) {
58
+ return false
59
+ }
60
+ return true
61
+ }
62
+
63
+ export function identitySummaryRows(
64
+ identity: EthagentIdentity | undefined,
65
+ config?: EthagentConfig,
66
+ ): Array<{
67
+ label: string
68
+ value: string
69
+ tone: 'ok' | 'dim'
70
+ }> {
71
+ const backup = identity?.backup
72
+ const owner = identity?.ownerAddress ?? identity?.address
73
+ const ownerValue = owner ? shortAddress(owner) : 'not connected'
74
+ const tokenValue = identity?.agentId ? `#${identity.agentId}` : 'not created'
75
+ const chain = chainSummaryRow(config, identity)
76
+ const stateValue = backup?.cid ? shortCid(backup.cid) : 'not saved yet'
77
+ const skillsValue = identity?.publicSkills?.cid ? shortCid(identity.publicSkills.cid) : 'not saved'
78
+ const cardValue = identity?.publicSkills?.agentCardCid ? shortCid(identity.publicSkills.agentCardCid) : 'not saved'
79
+ const iconValue = typeof identity?.state?.imageUrl === 'string' && identity.state.imageUrl.trim() ? 'attached' : 'not attached'
80
+ return [
81
+ { label: 'owner wallet', value: ownerValue, tone: identity ? 'ok' : 'dim' },
82
+ { label: 'token', value: tokenValue, tone: identity?.agentId ? 'ok' : 'dim' },
83
+ { label: 'network', value: chain.value, tone: chain.tone },
84
+ { label: 'state', value: stateValue, tone: backup ? 'ok' : 'dim' },
85
+ { label: 'skills', value: skillsValue, tone: identity?.publicSkills?.cid ? 'ok' : 'dim' },
86
+ { label: 'card', value: cardValue, tone: identity?.publicSkills?.agentCardCid ? 'ok' : 'dim' },
87
+ { label: 'icon', value: iconValue, tone: iconValue === 'attached' ? 'ok' : 'dim' },
88
+ ]
89
+ }
90
+
91
+ export function lastBackupLabel(identity?: EthagentIdentity): string {
92
+ const created = identity?.backup?.createdAt
93
+ return created ? formatDate(created) : 'never'
94
+ }
@@ -0,0 +1,32 @@
1
+ import type { EthagentConfig, EthagentIdentity, SelectableNetwork } from '../../../storage/config.js'
2
+ import { supportedErc8004ChainForId } from '../../registry/erc8004.js'
3
+ import { resolveSelectedNetwork } from '../../registry/registryConfig.js'
4
+
5
+ const NETWORK_LABELS: Record<SelectableNetwork, string> = {
6
+ mainnet: 'ethereum mainnet',
7
+ base: 'base',
8
+ }
9
+
10
+ export function networkLabel(network: SelectableNetwork): string {
11
+ return NETWORK_LABELS[network]
12
+ }
13
+
14
+ const NETWORK_SUBTITLES: Record<SelectableNetwork, string> = {
15
+ mainnet: 'Best for high-value identities and highest security',
16
+ base: 'Best for lower-cost setup and routine updates',
17
+ }
18
+
19
+ export function networkSubtitle(network: SelectableNetwork): string {
20
+ return NETWORK_SUBTITLES[network]
21
+ }
22
+
23
+ export function chainSummaryRow(config?: EthagentConfig, identity?: EthagentIdentity): {
24
+ label: string
25
+ value: string
26
+ tone: 'ok' | 'dim'
27
+ } {
28
+ const network = resolveSelectedNetwork(config)
29
+ const fromIdentity = identity?.chainId ? supportedErc8004ChainForId(identity.chainId)?.name.toLowerCase() : undefined
30
+ const value = fromIdentity ?? networkLabel(network)
31
+ return { label: 'chain', value, tone: identity?.chainId ? 'ok' : 'dim' }
32
+ }
@@ -0,0 +1,57 @@
1
+ import type { EthagentIdentity, TransferSnapshotMetadata } from '../../../storage/config.js'
2
+
3
+ export type TransferSnapshotView =
4
+ | {
5
+ kind: 'ready-to-transfer'
6
+ sender: string
7
+ receiver: string
8
+ receiverHandle?: string
9
+ slotCount: number
10
+ }
11
+ | {
12
+ kind: 'received'
13
+ sender: string
14
+ receiver: string
15
+ receiverHandle?: string
16
+ slotCount: number
17
+ }
18
+ | null
19
+
20
+ export function transferSnapshotView(identity?: EthagentIdentity | null): TransferSnapshotView {
21
+ const snapshot = identity?.backup?.transferSnapshot
22
+ if (!identity || !isDualWalletTransferSnapshot(snapshot)) return null
23
+ const owner = identity.ownerAddress ?? identity.address
24
+ if (!owner) return null
25
+ const ownerKey = owner.toLowerCase()
26
+ const senderKey = snapshot.senderAddress.toLowerCase()
27
+ const receiverKey = snapshot.receiverAddress.toLowerCase()
28
+ if (snapshot.slotCount < 2) return null
29
+ if (ownerKey === senderKey) {
30
+ return {
31
+ kind: 'ready-to-transfer',
32
+ sender: snapshot.senderAddress,
33
+ receiver: snapshot.receiverAddress,
34
+ ...(snapshot.receiverHandle ? { receiverHandle: snapshot.receiverHandle } : {}),
35
+ slotCount: snapshot.slotCount,
36
+ }
37
+ }
38
+ if (ownerKey === receiverKey) {
39
+ return {
40
+ kind: 'received',
41
+ sender: snapshot.senderAddress,
42
+ receiver: snapshot.receiverAddress,
43
+ ...(snapshot.receiverHandle ? { receiverHandle: snapshot.receiverHandle } : {}),
44
+ slotCount: snapshot.slotCount,
45
+ }
46
+ }
47
+ return null
48
+ }
49
+
50
+ function isDualWalletTransferSnapshot(value: unknown): value is TransferSnapshotMetadata {
51
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false
52
+ const snapshot = value as Partial<TransferSnapshotMetadata>
53
+ return snapshot.kind === 'dual-wallet'
54
+ && typeof snapshot.senderAddress === 'string'
55
+ && typeof snapshot.receiverAddress === 'string'
56
+ && typeof snapshot.slotCount === 'number'
57
+ }