ethagent 1.1.2 → 2.0.1

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 +124 -32
  3. package/package.json +8 -3
  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,131 @@
1
+ import { getAddress, isAddress, type Address } from 'viem'
2
+ import type { WalletContinuityRestoreAccessKey } from '../continuity/envelope.js'
3
+
4
+ export type ApprovedOperatorWalletRecord = {
5
+ address: Address
6
+ challenge?: string
7
+ verifiedAt?: string
8
+ restoreAccessKey?: WalletContinuityRestoreAccessKey
9
+ }
10
+
11
+ export type ApprovedOperatorWalletInput = string | ApprovedOperatorWalletRecord
12
+
13
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
14
+
15
+ function isZeroAddress(addr: string): boolean {
16
+ return addr.toLowerCase() === ZERO_ADDRESS
17
+ }
18
+
19
+ export function normalizeApprovedOperatorWallets(input: unknown): ApprovedOperatorWalletRecord[] {
20
+ if (!Array.isArray(input)) return []
21
+ const out: ApprovedOperatorWalletRecord[] = []
22
+ const seen = new Set<string>()
23
+ for (const item of input) {
24
+ const parsed = parseApprovedOperatorWalletRecord(item)
25
+ if (!parsed) continue
26
+ const key = parsed.address.toLowerCase()
27
+ if (seen.has(key)) continue
28
+ seen.add(key)
29
+ out.push(parsed)
30
+ }
31
+ return out
32
+ }
33
+
34
+ export function mergeApprovedOperatorWallets(
35
+ existing: unknown,
36
+ additions: unknown,
37
+ options?: { walletAddress?: string },
38
+ ): ApprovedOperatorWalletRecord[] {
39
+ let out = normalizeApprovedOperatorWallets(existing)
40
+ for (const record of normalizeApprovedOperatorWallets(additions)) {
41
+ out = upsertApprovedOperatorWallet(out, record, options)
42
+ }
43
+ return out
44
+ }
45
+
46
+ export function upsertApprovedOperatorWallet(
47
+ existing: unknown,
48
+ record: ApprovedOperatorWalletInput,
49
+ options?: { walletAddress?: string },
50
+ ): ApprovedOperatorWalletRecord[] {
51
+ const parsed = parseApprovedOperatorWalletRecord(record)
52
+ if (!parsed) throw new Error('Approved operator wallet must include a valid address')
53
+ if (options?.walletAddress && options.walletAddress.trim()) {
54
+ if (!isAddress(options.walletAddress, { strict: false })) throw new Error('Owner address is invalid')
55
+ if (parsed.address.toLowerCase() === options.walletAddress.toLowerCase()) {
56
+ throw new Error('Operator wallet must be different from the owner wallet')
57
+ }
58
+ }
59
+ const records = normalizeApprovedOperatorWallets(existing)
60
+ const key = parsed.address.toLowerCase()
61
+ const index = records.findIndex(item => item.address.toLowerCase() === key)
62
+ if (index === -1) return [...records, parsed]
63
+ const next = records.slice()
64
+ next[index] = { ...records[index], ...parsed, address: parsed.address }
65
+ return next
66
+ }
67
+
68
+ export function removeApprovedOperatorWallet(
69
+ existing: unknown,
70
+ address: string,
71
+ activeOperatorAddress?: string,
72
+ ): ApprovedOperatorWalletRecord[] {
73
+ if (!isAddress(address, { strict: false })) throw new Error('Operator wallet address is invalid')
74
+ const target = getAddress(address)
75
+ if (activeOperatorAddress && activeOperatorAddress.toLowerCase() === target.toLowerCase()) {
76
+ throw new Error('Set another active operator wallet before removing this wallet')
77
+ }
78
+ return normalizeApprovedOperatorWallets(existing).filter(item => item.address.toLowerCase() !== target.toLowerCase())
79
+ }
80
+
81
+ export function assertActiveOperatorIsApproved(
82
+ records: unknown,
83
+ activeOperatorAddress: string | undefined,
84
+ ): Address | undefined {
85
+ if (!activeOperatorAddress || !activeOperatorAddress.trim()) return undefined
86
+ if (!isAddress(activeOperatorAddress, { strict: false })) throw new Error('Active operator wallet address is invalid')
87
+ const active = getAddress(activeOperatorAddress)
88
+ const approved = normalizeApprovedOperatorWallets(records)
89
+ if (!approved.some(item => item.address.toLowerCase() === active.toLowerCase())) {
90
+ throw new Error('Active operator wallet must be one of the approved operator wallets')
91
+ }
92
+ return active
93
+ }
94
+
95
+ function parseApprovedOperatorWalletRecord(input: unknown): ApprovedOperatorWalletRecord | null {
96
+ if (typeof input === 'string') {
97
+ const trimmed = input.trim()
98
+ if (!isAddress(trimmed, { strict: false })) return null
99
+ if (isZeroAddress(trimmed)) return null
100
+ return { address: getAddress(trimmed) }
101
+ }
102
+ if (!input || typeof input !== 'object' || Array.isArray(input)) return null
103
+ const obj = input as Record<string, unknown>
104
+ const rawAddress = obj.address
105
+ if (typeof rawAddress !== 'string' || !isAddress(rawAddress, { strict: false })) return null
106
+ if (isZeroAddress(rawAddress)) return null
107
+ const challenge = typeof obj.challenge === 'string' && obj.challenge.trim() ? obj.challenge : undefined
108
+ const verifiedAt = typeof obj.verifiedAt === 'string' && obj.verifiedAt.trim() ? obj.verifiedAt : undefined
109
+ return {
110
+ address: getAddress(rawAddress),
111
+ ...(challenge ? { challenge } : {}),
112
+ ...(verifiedAt ? { verifiedAt } : {}),
113
+ ...(parseRestoreAccessKey(obj.restoreAccessKey) ? { restoreAccessKey: parseRestoreAccessKey(obj.restoreAccessKey)! } : {}),
114
+ }
115
+ }
116
+
117
+ function parseRestoreAccessKey(input: unknown): WalletContinuityRestoreAccessKey | undefined {
118
+ if (!input || typeof input !== 'object' || Array.isArray(input)) return undefined
119
+ const obj = input as Partial<WalletContinuityRestoreAccessKey>
120
+ if (typeof obj.address !== 'string' || !isAddress(obj.address, { strict: false })) return undefined
121
+ if (typeof obj.challenge !== 'string' || !obj.challenge.trim()) return undefined
122
+ if (typeof obj.salt !== 'string' || !obj.salt.trim()) return undefined
123
+ if (typeof obj.kemPublicKey !== 'string' || !obj.kemPublicKey.trim()) return undefined
124
+ return {
125
+ address: getAddress(obj.address),
126
+ challenge: obj.challenge,
127
+ salt: obj.salt,
128
+ kemPublicKey: obj.kemPublicKey,
129
+ ...(typeof obj.createdAt === 'string' && obj.createdAt.trim() ? { createdAt: obj.createdAt } : {}),
130
+ }
131
+ }
@@ -0,0 +1,46 @@
1
+ import { useEffect, useState } from 'react'
2
+ import type { EthagentConfig } from '../../../../storage/config.js'
3
+ import { emptyReconciliation, runReconciliation } from './run.js'
4
+ import type { AgentReconciliation } from './types.js'
5
+
6
+ export function useAgentReconciliation(
7
+ config: EthagentConfig | null,
8
+ ): { reconciliation: AgentReconciliation; refresh: () => void } {
9
+ const [reconciliation, setReconciliation] = useState<AgentReconciliation>(() => ({
10
+ token: 'no-agent',
11
+ custody: 'unknown',
12
+ agentUri: 'unknown',
13
+ ensRecords: 'unset',
14
+ vault: 'unset',
15
+ workingTree: 'unknown',
16
+ rpc: 'reachable',
17
+ driftCount: 0,
18
+ lastCheckedAt: new Date(0).toISOString(),
19
+ }))
20
+ const [refreshKey, setRefreshKey] = useState(0)
21
+
22
+ useEffect(() => {
23
+ if (!config?.identity || !config.identity.agentId || !config.identity.chainId || !config.identity.identityRegistryAddress) {
24
+ setReconciliation(emptyReconciliation())
25
+ return
26
+ }
27
+ const identity = config.identity
28
+ let cancelled = false
29
+ ;(async () => {
30
+ const result = await runReconciliation(identity, config)
31
+ if (cancelled) return
32
+ setReconciliation(result)
33
+ })()
34
+ return () => { cancelled = true }
35
+ }, [
36
+ config?.identity?.agentId,
37
+ config?.identity?.chainId,
38
+ config?.identity?.address,
39
+ config?.identity?.backup?.agentUri,
40
+ config?.identity?.state,
41
+ config?.erc8004?.operatorVaults,
42
+ refreshKey,
43
+ ])
44
+
45
+ return { reconciliation, refresh: () => setRefreshKey(k => k + 1) }
46
+ }
@@ -0,0 +1,129 @@
1
+ import { getAddress, type Address } from 'viem'
2
+ import {
3
+ createErc8004PublicClient,
4
+ validateErc8004TokenOwner,
5
+ type Erc8004RegistryConfig,
6
+ } from '../../../registry/erc8004.js'
7
+ import type { EthagentIdentity } from '../../../../storage/config.js'
8
+
9
+ export type OwnershipRole = 'token-holder' | 'vault-level-owner'
10
+
11
+ export type OwnershipGuardResult =
12
+ | { ok: true; effectiveOwner: Address; heldByVault: boolean }
13
+ | { ok: false; reason: 'not-owned' | 'lookup-failed'; detail: string; onChainOwner?: Address }
14
+
15
+ type OwnershipCacheEntry = {
16
+ expiresAt: number
17
+ result: OwnershipGuardResult
18
+ }
19
+
20
+ const OWNERSHIP_CACHE_TTL_MS = 30_000
21
+ const ownershipCache = new Map<string, OwnershipCacheEntry>()
22
+
23
+ function ownershipCacheKey(args: {
24
+ registry: Erc8004RegistryConfig
25
+ agentId: bigint
26
+ expectedSigner: Address
27
+ requiredRole: OwnershipRole
28
+ }): string {
29
+ return [
30
+ args.registry.chainId,
31
+ args.registry.identityRegistryAddress.toLowerCase(),
32
+ args.agentId.toString(),
33
+ args.expectedSigner.toLowerCase(),
34
+ args.requiredRole,
35
+ ].join('|')
36
+ }
37
+
38
+ export function invalidateOwnershipCache(): void {
39
+ ownershipCache.clear()
40
+ }
41
+
42
+ export async function preflightTokenOwnership(args: {
43
+ identity: EthagentIdentity
44
+ registry: Erc8004RegistryConfig
45
+ operatorVaults?: Readonly<Record<string, string>>
46
+ requiredRole: OwnershipRole
47
+ expectedSigner?: Address
48
+ }): Promise<OwnershipGuardResult> {
49
+ if (!args.identity.agentId) {
50
+ return { ok: false, reason: 'lookup-failed', detail: 'identity has no agentId' }
51
+ }
52
+ const expectedSigner = getAddress(args.expectedSigner ?? args.identity.ownerAddress ?? args.identity.address)
53
+ const agentId = BigInt(args.identity.agentId)
54
+ const key = ownershipCacheKey({ registry: args.registry, agentId, expectedSigner, requiredRole: args.requiredRole })
55
+ const cached = ownershipCache.get(key)
56
+ if (cached && cached.expiresAt > Date.now()) return cached.result
57
+ const result = await runOwnershipPreflight({ ...args, agentId, expectedSigner })
58
+ ownershipCache.set(key, { result, expiresAt: Date.now() + OWNERSHIP_CACHE_TTL_MS })
59
+ return result
60
+ }
61
+
62
+ async function runOwnershipPreflight(args: {
63
+ registry: Erc8004RegistryConfig
64
+ operatorVaults?: Readonly<Record<string, string>>
65
+ requiredRole: OwnershipRole
66
+ agentId: bigint
67
+ expectedSigner: Address
68
+ }): Promise<OwnershipGuardResult> {
69
+ const validation = await validateErc8004TokenOwner({
70
+ ...args.registry,
71
+ agentId: args.agentId,
72
+ expectedOwner: args.expectedSigner,
73
+ operatorVaults: args.operatorVaults,
74
+ })
75
+ if (validation.ok) {
76
+ const directOwner = await readDirectOwner({
77
+ registry: args.registry,
78
+ agentId: args.agentId,
79
+ })
80
+ if (directOwner.kind === 'error') {
81
+ return { ok: false, reason: 'lookup-failed', detail: directOwner.detail }
82
+ }
83
+ const heldByVault = directOwner.owner.toLowerCase() !== args.expectedSigner.toLowerCase()
84
+ if (args.requiredRole === 'token-holder' && heldByVault) {
85
+ return {
86
+ ok: false,
87
+ reason: 'not-owned',
88
+ detail: `Token is held by the operator delegation vault (${directOwner.owner}). Withdraw it first.`,
89
+ onChainOwner: directOwner.owner,
90
+ }
91
+ }
92
+ return { ok: true, effectiveOwner: validation.ownerAddress, heldByVault }
93
+ }
94
+ if (validation.reason === 'token-owner-lookup-failed') {
95
+ return { ok: false, reason: 'lookup-failed', detail: validation.detail }
96
+ }
97
+ return {
98
+ ok: false,
99
+ reason: 'not-owned',
100
+ detail: validation.detail,
101
+ onChainOwner: validation.ownerAddress,
102
+ }
103
+ }
104
+
105
+ async function readDirectOwner(args: {
106
+ registry: Erc8004RegistryConfig
107
+ agentId: bigint
108
+ }): Promise<{ kind: 'ok'; owner: Address } | { kind: 'error'; detail: string }> {
109
+ try {
110
+ const client = createErc8004PublicClient(args.registry)
111
+ const owner = await client.readContract({
112
+ address: args.registry.identityRegistryAddress,
113
+ abi: [
114
+ {
115
+ type: 'function',
116
+ name: 'ownerOf',
117
+ stateMutability: 'view',
118
+ inputs: [{ name: 'tokenId', type: 'uint256' }],
119
+ outputs: [{ name: '', type: 'address' }],
120
+ },
121
+ ] as const,
122
+ functionName: 'ownerOf',
123
+ args: [args.agentId],
124
+ })
125
+ return { kind: 'ok', owner: getAddress(owner as Address) }
126
+ } catch (err: unknown) {
127
+ return { kind: 'error', detail: err instanceof Error ? err.message : String(err) }
128
+ }
129
+ }
@@ -0,0 +1,302 @@
1
+ import { getAddress, type Address, type PublicClient } from 'viem'
2
+ import {
3
+ createErc8004PublicClient,
4
+ erc8004ConfigForSupportedChain,
5
+ validateErc8004TokenOwner,
6
+ type Erc8004RegistryConfig,
7
+ } from '../../../registry/erc8004.js'
8
+ import { isAgentInVault, resolveConfiguredOperatorVaultAddress } from '../../../registry/operatorVault.js'
9
+ import type { EthagentConfig, EthagentIdentity } from '../../../../storage/config.js'
10
+ import { readOperatorVaultAddressField } from '../../../identityCompat.js'
11
+ import { reconcileWalletSetup, type RecordsFixPlan } from '../walletSetup.js'
12
+ import { readCustodyMode } from '../../model/custody.js'
13
+ import { continuityWorkingTreeStatus, type ContinuityWorkingTreeStatus } from '../../../continuity/storage.js'
14
+ import type { AgentReconciliation } from './types.js'
15
+
16
+ export function emptyReconciliation(): AgentReconciliation {
17
+ return {
18
+ token: 'no-agent',
19
+ custody: 'unknown',
20
+ agentUri: 'unknown',
21
+ ensRecords: 'unset',
22
+ vault: 'unset',
23
+ workingTree: 'unknown',
24
+ rpc: 'reachable',
25
+ driftCount: 0,
26
+ lastCheckedAt: new Date().toISOString(),
27
+ }
28
+ }
29
+
30
+ export async function runReconciliation(
31
+ identity: EthagentIdentity,
32
+ config: EthagentConfig,
33
+ ): Promise<AgentReconciliation> {
34
+ const fallback = (() => {
35
+ try { return erc8004ConfigForSupportedChain(identity.chainId!) }
36
+ catch { return null }
37
+ })()
38
+ const rpcUrl = identity.rpcUrl ?? config.erc8004?.rpcUrl ?? fallback?.rpcUrl ?? ''
39
+ const registry: Erc8004RegistryConfig = {
40
+ chainId: identity.chainId!,
41
+ rpcUrl,
42
+ identityRegistryAddress: identity.identityRegistryAddress! as `0x${string}`,
43
+ }
44
+ const expectedOwner = getAddress(identity.ownerAddress ?? identity.address) as Address
45
+ const agentId = BigInt(identity.agentId!)
46
+ const operatorVaults = config.erc8004?.operatorVaults
47
+ const vaultAddress = resolveReconciliationVaultAddress(identity, operatorVaults)
48
+
49
+ if (!rpcUrl) {
50
+ return {
51
+ token: 'unknown',
52
+ tokenDetail: `no rpcUrl configured for chain ${identity.chainId}`,
53
+ custody: 'unknown',
54
+ agentUri: 'unknown',
55
+ ensRecords: 'unknown',
56
+ vault: vaultAddress ? 'unknown' : 'unset',
57
+ workingTree: 'unknown',
58
+ rpc: 'failing',
59
+ driftCount: 0,
60
+ lastCheckedAt: new Date().toISOString(),
61
+ }
62
+ }
63
+
64
+ const client = createErc8004PublicClient(registry)
65
+
66
+ const [
67
+ tokenResult,
68
+ custodyResult,
69
+ agentUriResult,
70
+ ensReconcileResult,
71
+ vaultResult,
72
+ workingTreeResult,
73
+ ] = await Promise.allSettled([
74
+ probeToken({ registry, agentId, expectedOwner, operatorVaults }),
75
+ probeCustody({ client, registry, agentId, expectedOwner, vaultAddress, identity }),
76
+ probeAgentUri({ client, registry, agentId, identity }),
77
+ probeEnsRecords({ identity, registry, client }),
78
+ probeVault({ client, vaultAddress }),
79
+ probeWorkingTree(identity),
80
+ ])
81
+
82
+ const token = unwrap(tokenResult, fallbackToken)
83
+ const custody = unwrap(custodyResult, () => ({ kind: 'unknown' as const }))
84
+ const agentUri = unwrap(agentUriResult, () => ({ kind: 'unknown' as const }))
85
+ const ensRecords = unwrap(ensReconcileResult, () => ({ kind: 'unknown' as const }))
86
+ const vault = unwrap(vaultResult, () => ({ kind: vaultAddress ? 'unknown' as const : 'unset' as const }))
87
+ const workingTree = unwrap(workingTreeResult, () => ({ kind: 'unknown' as const }))
88
+
89
+ const allFailed = [tokenResult, custodyResult, agentUriResult, ensReconcileResult, vaultResult]
90
+ .every(r => r.status === 'rejected')
91
+ const rpc: 'reachable' | 'failing' = allFailed ? 'failing' : 'reachable'
92
+
93
+ const recon: AgentReconciliation = {
94
+ token: token.kind,
95
+ ...(token.kind === 'unlinked' && token.detail ? { tokenDetail: token.detail } : {}),
96
+ ...(token.kind === 'unlinked' ? { tokenAgentId: token.agentId } : {}),
97
+ ...(token.kind === 'linked' ? { onChainOwner: token.onChainOwner } : {}),
98
+ ...(token.kind === 'unlinked' && token.onChainOwner ? { onChainOwner: token.onChainOwner } : {}),
99
+ ...(token.kind === 'unknown' && token.detail ? { tokenDetail: token.detail } : {}),
100
+ custody: custody.kind,
101
+ agentUri: agentUri.kind,
102
+ ensRecords: ensRecords.kind,
103
+ ...(ensRecords.kind === 'drift' || ensRecords.kind === 'aligned' ? { ensRecordsPlan: ensRecords.plan } : {}),
104
+ vault: vault.kind,
105
+ workingTree: workingTree.kind,
106
+ rpc,
107
+ driftCount: 0,
108
+ lastCheckedAt: new Date().toISOString(),
109
+ }
110
+ recon.driftCount = computeDriftCount(recon)
111
+ return recon
112
+ }
113
+
114
+ function resolveReconciliationVaultAddress(
115
+ identity: EthagentIdentity,
116
+ operatorVaults?: Readonly<Record<string, string>>,
117
+ ): Address | undefined {
118
+ const identityVault = readOperatorVaultAddressField(identity.state as Record<string, unknown> | undefined)
119
+ if (identityVault) return getAddress(identityVault)
120
+ if (readCustodyMode(identity.state as Record<string, unknown> | undefined) !== 'advanced') return undefined
121
+ if (!identity.chainId) return undefined
122
+ return resolveConfiguredOperatorVaultAddress(operatorVaults, identity.chainId)
123
+ }
124
+
125
+ type TokenProbe =
126
+ | { kind: 'linked'; onChainOwner: string }
127
+ | { kind: 'unlinked'; detail: string; agentId: string; onChainOwner?: string }
128
+ | { kind: 'unknown'; detail: string }
129
+ | { kind: 'no-agent' }
130
+
131
+ async function probeToken(args: {
132
+ registry: Erc8004RegistryConfig
133
+ agentId: bigint
134
+ expectedOwner: Address
135
+ operatorVaults?: Readonly<Record<string, string>>
136
+ }): Promise<TokenProbe> {
137
+ const result = await validateErc8004TokenOwner({
138
+ ...args.registry,
139
+ agentId: args.agentId,
140
+ expectedOwner: args.expectedOwner,
141
+ operatorVaults: args.operatorVaults,
142
+ })
143
+ if (result.ok) return { kind: 'linked', onChainOwner: result.ownerAddress }
144
+ if (result.reason === 'token-owner-lookup-failed') return { kind: 'unknown', detail: result.detail }
145
+ return {
146
+ kind: 'unlinked',
147
+ detail: result.detail,
148
+ agentId: args.agentId.toString(),
149
+ ...(result.ownerAddress ? { onChainOwner: result.ownerAddress } : {}),
150
+ }
151
+ }
152
+
153
+ function fallbackToken(): TokenProbe {
154
+ return { kind: 'unknown', detail: 'token ownership probe failed' }
155
+ }
156
+
157
+ type CustodyProbe = { kind: 'simple' | 'advanced' | 'withdrawn' | 'mid-flow-uri-pending' | 'unknown' }
158
+
159
+ async function probeCustody(args: {
160
+ client: PublicClient
161
+ registry: Erc8004RegistryConfig
162
+ agentId: bigint
163
+ expectedOwner: Address
164
+ vaultAddress?: Address
165
+ identity: EthagentIdentity
166
+ }): Promise<CustodyProbe> {
167
+ if (!args.vaultAddress) return { kind: 'simple' }
168
+ try {
169
+ const status = await isAgentInVault({
170
+ client: args.client,
171
+ vaultAddress: args.vaultAddress,
172
+ registry: args.registry.identityRegistryAddress,
173
+ agentId: args.agentId,
174
+ })
175
+ if (!status.inVault) return { kind: 'withdrawn' }
176
+ const localUri = args.identity.agentUri ?? args.identity.backup?.agentUri
177
+ if (localUri) {
178
+ try {
179
+ const onChain = await args.client.readContract({
180
+ address: args.registry.identityRegistryAddress,
181
+ abi: ERC8004_AGENT_URI_ABI,
182
+ functionName: 'agentURI',
183
+ args: [args.agentId],
184
+ }) as string
185
+ if (onChain && onChain !== localUri) {
186
+ return { kind: 'mid-flow-uri-pending' }
187
+ }
188
+ } catch {
189
+ }
190
+ }
191
+ if (status.ownerAddress?.toLowerCase() === args.expectedOwner.toLowerCase()) {
192
+ return { kind: 'advanced' }
193
+ }
194
+ return { kind: 'advanced' }
195
+ } catch {
196
+ return { kind: 'unknown' }
197
+ }
198
+ }
199
+
200
+ type AgentUriProbe = { kind: 'in-sync' | 'chain-newer' | 'local-newer' | 'unknown' }
201
+
202
+ const ERC8004_AGENT_URI_ABI = [
203
+ {
204
+ type: 'function',
205
+ name: 'agentURI',
206
+ stateMutability: 'view',
207
+ inputs: [{ name: 'agentId', type: 'uint256' }],
208
+ outputs: [{ name: '', type: 'string' }],
209
+ },
210
+ ] as const
211
+
212
+ async function probeAgentUri(args: {
213
+ client: PublicClient
214
+ registry: Erc8004RegistryConfig
215
+ agentId: bigint
216
+ identity: EthagentIdentity
217
+ }): Promise<AgentUriProbe> {
218
+ const localUri = args.identity.agentUri ?? args.identity.backup?.agentUri
219
+ if (!localUri) return { kind: 'unknown' }
220
+ try {
221
+ const onChain = await args.client.readContract({
222
+ address: args.registry.identityRegistryAddress,
223
+ abi: ERC8004_AGENT_URI_ABI,
224
+ functionName: 'agentURI',
225
+ args: [args.agentId],
226
+ }) as string
227
+ if (onChain === localUri) return { kind: 'in-sync' }
228
+ if (!onChain) return { kind: 'local-newer' }
229
+ return { kind: 'local-newer' }
230
+ } catch {
231
+ return { kind: 'unknown' }
232
+ }
233
+ }
234
+
235
+ type EnsRecordsProbe =
236
+ | { kind: 'aligned'; plan: RecordsFixPlan }
237
+ | { kind: 'drift'; plan: RecordsFixPlan }
238
+ | { kind: 'unset' }
239
+ | { kind: 'unknown' }
240
+
241
+ async function probeEnsRecords(args: {
242
+ identity: EthagentIdentity
243
+ registry: Erc8004RegistryConfig
244
+ client: PublicClient
245
+ }): Promise<EnsRecordsProbe> {
246
+ const baseState = (args.identity.state ?? {}) as Record<string, unknown>
247
+ const ensName = typeof baseState.ensName === 'string' ? baseState.ensName.trim() : ''
248
+ const custody = readCustodyMode(baseState)
249
+ if (!ensName || custody !== 'advanced') return { kind: 'unset' }
250
+ try {
251
+ const plan = await reconcileWalletSetup({ identity: args.identity, registry: args.registry })
252
+ if (plan.items.length === 0) return { kind: 'aligned', plan }
253
+ const actionable = plan.items.some(item => item.kind === 'missing-approval' || item.kind === 'stale-approval')
254
+ return actionable ? { kind: 'drift', plan } : { kind: 'aligned', plan }
255
+ } catch {
256
+ return { kind: 'unknown' }
257
+ }
258
+ }
259
+
260
+ type VaultProbe = { kind: 'confirmed' | 'missing' | 'unset' | 'unknown' }
261
+
262
+ async function probeVault(args: {
263
+ client: PublicClient
264
+ vaultAddress?: Address
265
+ }): Promise<VaultProbe> {
266
+ if (!args.vaultAddress) return { kind: 'unset' }
267
+ try {
268
+ const code = await args.client.getBytecode({ address: args.vaultAddress })
269
+ if (!code || code === '0x') return { kind: 'missing' }
270
+ return { kind: 'confirmed' }
271
+ } catch {
272
+ return { kind: 'unknown' }
273
+ }
274
+ }
275
+
276
+ type WorkingTreeProbe = { kind: 'clean' | 'dirty' | 'unknown' }
277
+
278
+ async function probeWorkingTree(identity: EthagentIdentity): Promise<WorkingTreeProbe> {
279
+ try {
280
+ const status: ContinuityWorkingTreeStatus = await continuityWorkingTreeStatus(identity)
281
+ if (!status.ready) return { kind: 'unknown' }
282
+ return status.localChangedAfterBackup ? { kind: 'dirty' } : { kind: 'clean' }
283
+ } catch {
284
+ return { kind: 'unknown' }
285
+ }
286
+ }
287
+
288
+ function unwrap<T>(result: PromiseSettledResult<T>, fallback: () => T): T {
289
+ if (result.status === 'fulfilled') return result.value
290
+ return fallback()
291
+ }
292
+
293
+ function computeDriftCount(r: AgentReconciliation): number {
294
+ let n = 0
295
+ if (r.token === 'unlinked') n++
296
+ if (r.custody === 'mid-flow-uri-pending') n++
297
+ if (r.agentUri === 'local-newer' || r.agentUri === 'chain-newer') n++
298
+ if (r.ensRecords === 'drift') n++
299
+ if (r.vault === 'missing') n++
300
+ if (r.workingTree === 'dirty') n++
301
+ return n
302
+ }
@@ -0,0 +1,17 @@
1
+ import type { RecordsFixPlan } from '../walletSetup.js'
2
+
3
+ export type AgentReconciliation = {
4
+ token: 'linked' | 'unlinked' | 'unknown' | 'no-agent'
5
+ tokenDetail?: string
6
+ tokenAgentId?: string
7
+ onChainOwner?: string
8
+ custody: 'simple' | 'advanced' | 'withdrawn' | 'mid-flow-uri-pending' | 'unknown'
9
+ agentUri: 'in-sync' | 'chain-newer' | 'local-newer' | 'unknown'
10
+ ensRecords: 'aligned' | 'drift' | 'unset' | 'unknown'
11
+ ensRecordsPlan?: RecordsFixPlan
12
+ vault: 'confirmed' | 'missing' | 'unset' | 'unknown'
13
+ workingTree: 'clean' | 'dirty' | 'unknown'
14
+ rpc: 'reachable' | 'failing'
15
+ driftCount: number
16
+ lastCheckedAt: string
17
+ }
@@ -0,0 +1,21 @@
1
+ export {
2
+ invalidateOwnershipCache,
3
+ preflightTokenOwnership,
4
+ useAgentReconciliation,
5
+ } from './useAgentReconciliation.js'
6
+ export type {
7
+ AgentReconciliation,
8
+ } from './useAgentReconciliation.js'
9
+ export {
10
+ computeApprovalDiff,
11
+ describeFixPlanItem,
12
+ encodeResolverApprovalChanges,
13
+ fixPlanRequiresOwnerWallet,
14
+ reconcileWalletSetup,
15
+ verifyResolverApprovalsLanded,
16
+ } from './walletSetup.js'
17
+ export type {
18
+ ApprovalDiff,
19
+ RecordsFixPlan,
20
+ RecordsFixPlanItem,
21
+ } from './walletSetup.js'
@@ -0,0 +1,10 @@
1
+ export { useAgentReconciliation } from './agentReconciliation/hook.js'
2
+ export type { AgentReconciliation } from './agentReconciliation/types.js'
3
+ export {
4
+ invalidateOwnershipCache,
5
+ preflightTokenOwnership,
6
+ } from './agentReconciliation/ownership.js'
7
+ export type {
8
+ OwnershipGuardResult,
9
+ OwnershipRole,
10
+ } from './agentReconciliation/ownership.js'