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.
- package/LICENSE +21 -21
- package/README.md +126 -30
- package/package.json +7 -2
- package/src/app/FirstRun.tsx +190 -146
- package/src/app/FirstRunTimeline.tsx +47 -0
- package/src/app/input/AppInputProvider.tsx +1 -1
- package/src/app/keybindings/KeybindingProvider.tsx +1 -1
- package/src/chat/ChatBottomPane.tsx +0 -1
- package/src/chat/ChatInput.tsx +6 -6
- package/src/chat/ChatScreen.tsx +35 -15
- package/src/chat/ContextLimitView.tsx +4 -4
- package/src/chat/ContinuityEditReviewView.tsx +10 -22
- package/src/chat/CopyPicker.tsx +0 -1
- package/src/chat/MessageList.tsx +62 -45
- package/src/chat/PermissionPrompt.tsx +13 -9
- package/src/chat/PlanApprovalView.tsx +3 -3
- package/src/chat/ResumeView.tsx +1 -4
- package/src/chat/RewindView.tsx +2 -2
- package/src/chat/chatInputState.ts +1 -1
- package/src/chat/chatScreenUtils.ts +22 -11
- package/src/chat/chatSessionState.ts +2 -2
- package/src/chat/chatTurnOrchestrator.ts +16 -81
- package/src/chat/commands.ts +1 -1
- package/src/chat/textCursor.ts +1 -1
- package/src/chat/transcriptViewport.ts +2 -7
- package/src/cli/ResetConfirmView.tsx +1 -1
- package/src/cli/main.tsx +9 -3
- package/src/cli/preview.tsx +0 -5
- package/src/cli/updateNotice.ts +4 -2
- package/src/identity/continuity/editor.ts +7 -107
- package/src/identity/continuity/envelope.ts +1048 -40
- package/src/identity/continuity/history.ts +4 -4
- package/src/identity/continuity/localBackup.ts +249 -0
- package/src/identity/continuity/privateEdit/apply.ts +170 -0
- package/src/identity/continuity/privateEdit/diff.ts +82 -0
- package/src/identity/continuity/privateEdit/files.ts +23 -0
- package/src/identity/continuity/privateEdit/types.ts +28 -0
- package/src/identity/continuity/privateEdit.ts +10 -298
- package/src/identity/continuity/publicSkills.ts +8 -9
- package/src/identity/continuity/snapshots.ts +17 -6
- package/src/identity/continuity/storage/defaults.ts +111 -0
- package/src/identity/continuity/storage/files.ts +72 -0
- package/src/identity/continuity/storage/markdown.ts +81 -0
- package/src/identity/continuity/storage/paths.ts +24 -0
- package/src/identity/continuity/storage/scaffold.ts +124 -0
- package/src/identity/continuity/storage/status.ts +86 -0
- package/src/identity/continuity/storage/types.ts +27 -0
- package/src/identity/continuity/storage.ts +32 -507
- package/src/identity/continuity/zipWriter.ts +95 -0
- package/src/identity/crypto/backupEnvelope.ts +14 -247
- package/src/identity/crypto/eth.ts +7 -7
- package/src/identity/ens/agentRecords.ts +96 -0
- package/src/identity/ens/ensAutomation/contracts.ts +38 -0
- package/src/identity/ens/ensAutomation/delete.ts +80 -0
- package/src/identity/ens/ensAutomation/names.ts +14 -0
- package/src/identity/ens/ensAutomation/operators.ts +29 -0
- package/src/identity/ens/ensAutomation/read.ts +114 -0
- package/src/identity/ens/ensAutomation/root.ts +63 -0
- package/src/identity/ens/ensAutomation/setup.ts +284 -0
- package/src/identity/ens/ensAutomation/transactions.ts +107 -0
- package/src/identity/ens/ensAutomation/types.ts +126 -0
- package/src/identity/ens/ensAutomation.ts +29 -0
- package/src/identity/ens/ensLookup/client.ts +43 -0
- package/src/identity/ens/ensLookup/constants.ts +26 -0
- package/src/identity/ens/ensLookup/discovery.ts +70 -0
- package/src/identity/ens/ensLookup/names.ts +34 -0
- package/src/identity/ens/ensLookup/records.ts +45 -0
- package/src/identity/ens/ensLookup/resolve.ts +75 -0
- package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
- package/src/identity/ens/ensLookup/types.ts +38 -0
- package/src/identity/ens/ensLookup/validation.ts +72 -0
- package/src/identity/ens/ensLookup.ts +19 -0
- package/src/identity/ens/ensRegistration.ts +199 -0
- package/src/identity/ens/resolverDelegation.ts +48 -0
- package/src/identity/hub/IdentityHub.tsx +13 -817
- package/src/identity/hub/OperationalRoutes.tsx +370 -0
- package/src/identity/hub/Routes.tsx +361 -0
- package/src/identity/hub/advancedEnsValidation.ts +45 -0
- package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
- package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
- package/src/identity/hub/components/FlowTimeline.tsx +27 -0
- package/src/identity/hub/components/IdentitySummary.tsx +190 -0
- package/src/identity/hub/components/MenuScreen.tsx +237 -0
- package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
- package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
- package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
- package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
- package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
- package/src/identity/hub/effects/create.ts +310 -0
- package/src/identity/hub/effects/ens/flows.ts +218 -0
- package/src/identity/hub/effects/ens/index.ts +11 -0
- package/src/identity/hub/effects/ens/transactions.ts +239 -0
- package/src/identity/hub/effects/index.ts +74 -0
- package/src/identity/hub/effects/profile/profileState.ts +173 -0
- package/src/identity/hub/effects/publicProfile/index.ts +5 -0
- package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
- package/src/identity/hub/effects/rebackup/index.ts +7 -0
- package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
- package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
- package/src/identity/hub/effects/receipts.ts +46 -0
- package/src/identity/hub/effects/restore/apply.ts +112 -0
- package/src/identity/hub/effects/restore/auth.ts +159 -0
- package/src/identity/hub/effects/restore/discover.ts +86 -0
- package/src/identity/hub/effects/restore/envelopes.ts +21 -0
- package/src/identity/hub/effects/restore/fetch.ts +25 -0
- package/src/identity/hub/effects/restore/index.ts +22 -0
- package/src/identity/hub/effects/restore/recovery.ts +135 -0
- package/src/identity/hub/effects/restore/resolve.ts +102 -0
- package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
- package/src/identity/hub/effects/restore/shared.ts +91 -0
- package/src/identity/hub/effects/restoreAdmin.ts +93 -0
- package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
- package/src/identity/hub/effects/shared/snapshot.ts +336 -0
- package/src/identity/hub/effects/shared/sync.ts +190 -0
- package/src/identity/hub/effects/token-transfer/index.ts +6 -0
- package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
- package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
- package/src/identity/hub/effects/types.ts +53 -0
- package/src/identity/hub/effects/vault/preflight.ts +50 -0
- package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
- package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
- package/src/identity/hub/{screens → flows/continuity}/RecoveryConfirmScreen.tsx +28 -19
- package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
- package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
- package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
- package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
- package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
- package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
- package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
- package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
- package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
- package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
- package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
- package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
- package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
- package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
- package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
- package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
- package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
- package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
- package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
- package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
- package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
- package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
- package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
- package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
- package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +23 -44
- package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
- package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
- package/src/identity/hub/identityHubReducer.ts +164 -99
- package/src/identity/hub/model/continuity.ts +94 -0
- package/src/identity/hub/model/copy.ts +35 -0
- package/src/identity/hub/model/custody.ts +54 -0
- package/src/identity/hub/model/ens.ts +49 -0
- package/src/identity/hub/model/errors.ts +140 -0
- package/src/identity/hub/model/format.ts +15 -0
- package/src/identity/hub/model/identity.ts +94 -0
- package/src/identity/hub/model/network.ts +32 -0
- package/src/identity/hub/model/transfer.ts +57 -0
- package/src/identity/hub/operatorWallets.ts +131 -0
- package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
- package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
- package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
- package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
- package/src/identity/hub/reconciliation/index.ts +21 -0
- package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
- package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
- package/src/identity/hub/txGuard.ts +51 -0
- package/src/identity/hub/types.ts +17 -0
- package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
- package/src/identity/hub/useIdentityHubController.ts +396 -0
- package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
- package/src/identity/hub/utils.ts +79 -0
- package/src/identity/identityCompat.ts +34 -0
- package/src/identity/profile/agentIcon.ts +61 -0
- package/src/identity/profile/imagePicker.ts +12 -12
- package/src/identity/registry/erc8004/abi.ts +14 -0
- package/src/identity/registry/erc8004/chains.ts +150 -0
- package/src/identity/registry/erc8004/client.ts +11 -0
- package/src/identity/registry/erc8004/discovery.ts +511 -0
- package/src/identity/registry/erc8004/metadata.ts +335 -0
- package/src/identity/registry/erc8004/ownership.ts +121 -0
- package/src/identity/registry/erc8004/preflight.ts +123 -0
- package/src/identity/registry/erc8004/transactions.ts +77 -0
- package/src/identity/registry/erc8004/types.ts +88 -0
- package/src/identity/registry/erc8004/uri.ts +59 -0
- package/src/identity/registry/erc8004/utils.ts +58 -0
- package/src/identity/registry/erc8004.ts +53 -1106
- package/src/identity/registry/fieldParsers.ts +28 -0
- package/src/identity/registry/operatorVault/bytecode.ts +98 -0
- package/src/identity/registry/operatorVault/constants.ts +38 -0
- package/src/identity/registry/operatorVault/read.ts +246 -0
- package/src/identity/registry/operatorVault/transactions.ts +81 -0
- package/src/identity/registry/operatorVault.ts +44 -0
- package/src/identity/storage/ipfs.ts +26 -24
- package/src/identity/wallet/browserWallet/gas.ts +41 -0
- package/src/identity/wallet/browserWallet/html.ts +106 -0
- package/src/identity/wallet/browserWallet/http.ts +28 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
- package/src/identity/wallet/browserWallet/requests.ts +191 -0
- package/src/identity/wallet/browserWallet/session.ts +325 -0
- package/src/identity/wallet/browserWallet/types.ts +192 -0
- package/src/identity/wallet/browserWallet/validation.ts +74 -0
- package/src/identity/wallet/browserWallet.ts +30 -393
- package/src/identity/wallet/page/constants.ts +5 -0
- package/src/identity/wallet/page/controller.ts +251 -0
- package/src/identity/wallet/page/copy.ts +340 -0
- package/src/identity/wallet/page/grainient.ts +278 -0
- package/src/identity/wallet/page/html.ts +28 -0
- package/src/identity/wallet/page/markup.ts +50 -0
- package/src/identity/wallet/page/state.ts +9 -0
- package/src/identity/wallet/page/styles/base.ts +259 -0
- package/src/identity/wallet/page/styles/components.ts +262 -0
- package/src/identity/wallet/page/styles/index.ts +5 -0
- package/src/identity/wallet/page/styles/responsive.ts +247 -0
- package/src/identity/wallet/page/types.ts +47 -0
- package/src/identity/wallet/page/view.ts +535 -0
- package/src/identity/wallet/page/walletProvider.ts +70 -0
- package/src/identity/wallet/page.tsx +38 -0
- package/src/identity/wallet/walletPurposeCompat.ts +27 -0
- package/src/mcp/manager.ts +0 -1
- package/src/models/ModelPicker.tsx +36 -30
- package/src/models/catalog.ts +5 -2
- package/src/models/huggingface.ts +9 -9
- package/src/models/llamacpp.ts +13 -13
- package/src/models/modelDisplay.ts +75 -0
- package/src/models/modelPickerOptions.ts +16 -3
- package/src/models/modelRecommendation.ts +0 -1
- package/src/providers/errors.ts +16 -0
- package/src/providers/gemini.ts +252 -39
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +1 -1
- package/src/runtime/sessionMode.ts +1 -1
- package/src/runtime/systemPrompt.ts +2 -0
- package/src/runtime/toolExecution.ts +18 -22
- package/src/runtime/toolIntent.ts +0 -20
- package/src/runtime/turn.ts +0 -92
- package/src/storage/atomicWrite.ts +4 -1
- package/src/storage/config.ts +181 -5
- package/src/storage/identity.ts +9 -3
- package/src/storage/secrets.ts +2 -2
- package/src/tools/bashSafety.ts +8 -0
- package/src/tools/changeDirectoryTool.ts +1 -1
- package/src/tools/deleteFileTool.ts +4 -4
- package/src/tools/editTool.ts +4 -4
- package/src/tools/editUtils.ts +5 -5
- package/src/tools/privateContinuityEditTool.ts +4 -5
- package/src/tools/privateContinuityReadTool.ts +1 -2
- package/src/tools/registry.ts +30 -0
- package/src/tools/writeFileTool.ts +5 -5
- package/src/ui/BrandSplash.tsx +20 -85
- package/src/ui/ProgressBar.tsx +3 -5
- package/src/ui/Select.tsx +20 -8
- package/src/ui/Spinner.tsx +38 -3
- package/src/ui/Surface.tsx +2 -2
- package/src/ui/TextInput.tsx +63 -20
- package/src/ui/theme.ts +7 -34
- package/src/utils/openExternal.ts +21 -0
- package/src/utils/withRetry.ts +47 -3
- package/src/identity/hub/identityHubEffects.ts +0 -937
- package/src/identity/hub/identityHubModel.ts +0 -371
- package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -156
- package/src/identity/hub/screens/EditProfileFlow.tsx +0 -146
- package/src/identity/hub/screens/IdentitySummary.tsx +0 -106
- package/src/identity/hub/screens/MenuScreen.tsx +0 -117
- package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
- package/src/identity/wallet/wallet-page/wallet.html +0 -1202
- /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
|
+
}
|