ethagent 3.0.0 → 3.0.2

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 (30) hide show
  1. package/package.json +1 -1
  2. package/src/chat/ChatScreen.tsx +17 -1
  3. package/src/cli/main.tsx +7 -0
  4. package/src/identity/continuity/envelope.ts +9 -9
  5. package/src/identity/continuity/publicSkills.ts +17 -0
  6. package/src/identity/hub/OperationalRoutes.tsx +11 -39
  7. package/src/identity/hub/continuity/ContinuityDashboardScreen.tsx +3 -23
  8. package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +5 -5
  9. package/src/identity/hub/continuity/SavePromptScreen.tsx +1 -3
  10. package/src/identity/hub/continuity/skills/SkillActionsScreen.tsx +151 -0
  11. package/src/identity/hub/continuity/skills/SkillsTreeScreen.tsx +1 -33
  12. package/src/identity/hub/continuity/state.ts +9 -9
  13. package/src/identity/hub/create/CreateFlow.tsx +1 -1
  14. package/src/identity/hub/custody/routes.tsx +1 -1
  15. package/src/identity/hub/ens/EnsEditAdvancedScreens.tsx +0 -1
  16. package/src/identity/hub/ens/EnsEditMaintenanceScreens.tsx +0 -1
  17. package/src/identity/hub/identityHubReducer.ts +3 -9
  18. package/src/identity/hub/profile/EditProfileFlow.tsx +5 -5
  19. package/src/identity/hub/restore/recovery.ts +3 -3
  20. package/src/identity/hub/shared/components/IdentitySummary.tsx +22 -2
  21. package/src/identity/hub/shared/components/MenuScreen.tsx +4 -4
  22. package/src/identity/hub/shared/components/UnlinkedIdentityScreen.tsx +3 -3
  23. package/src/identity/hub/shared/components/menuFlagsFromReconciliation.ts +1 -1
  24. package/src/identity/hub/useIdentityHubContinuity.ts +3 -3
  25. package/src/identity/registry/erc8004/metadata.ts +31 -23
  26. package/src/identity/wallet/page/copy.ts +43 -43
  27. package/src/models/modelPickerOptions.ts +2 -0
  28. package/src/ui/terminalTitle.ts +30 -0
  29. package/src/identity/hub/continuity/skills/DeleteSkillScreen.tsx +0 -123
  30. package/src/identity/hub/continuity/skills/SkillVisibilityScreen.tsx +0 -171
@@ -143,7 +143,7 @@ export const CreateFlow: React.FC<CreateFlowProps> = ({
143
143
  <BusyScreen
144
144
  title="Getting Ready"
145
145
  subtitle={indicator}
146
- label="checking IPFS storage and chain..."
146
+ label="checking IPFS storage and onchain..."
147
147
  onCancel={onBack}
148
148
  />
149
149
  )
@@ -54,7 +54,7 @@ export function renderCustodyStep({
54
54
  footer={<Text color={theme.dim}>esc cancel</Text>}
55
55
  >
56
56
  <Box marginTop={1}>
57
- <Text color={theme.textSubtle}>Reading vault state from chain...</Text>
57
+ <Text color={theme.textSubtle}>Reading vault state from onchain...</Text>
58
58
  </Box>
59
59
  </Surface>
60
60
  )
@@ -19,7 +19,6 @@ import { shortAddress } from '../shared/model/format.js'
19
19
  import {
20
20
  footerHint,
21
21
  } from './EnsEditShared.js'
22
- import { IdentitySummary } from '../shared/components/IdentitySummary.js'
23
22
  import {
24
23
  EnsSetupBlockedScreen,
25
24
  EnsSetupReviewScreen,
@@ -19,7 +19,6 @@ import {
19
19
  EnsSetupRow,
20
20
  footerHint,
21
21
  } from './EnsEditShared.js'
22
- import { IdentitySummary } from '../shared/components/IdentitySummary.js'
23
22
  import { UnlinkEnsReviewScreen } from './EnsEditReviewScreens.js'
24
23
  import {
25
24
  DeleteSubdomainTxRunner,
@@ -62,10 +62,8 @@ export type Step =
62
62
  | { kind: 'continuity-skills-tree'; notice?: string; editorOpened?: boolean }
63
63
  | { kind: 'continuity-skill-new'; error?: string }
64
64
  | { kind: 'continuity-skill-new-visibility'; name: string; error?: string }
65
- | { kind: 'continuity-skill-delete'; notice?: string }
65
+ | { kind: 'continuity-skill-actions'; relativePath: string; notice?: string }
66
66
  | { kind: 'continuity-skill-delete-confirm'; target: { kind: 'skill'; relativePath: string } }
67
- | { kind: 'continuity-skill-visibility'; notice?: string }
68
- | { kind: 'continuity-skill-visibility-pick'; relativePath: string }
69
67
  | { kind: 'rebackup-confirm'; back: Step }
70
68
  | { kind: 'recovery-refetch-confirm'; back: Step }
71
69
  | { kind: 'recovery-refetching'; identity: EthagentIdentity; registry: Erc8004RegistryConfig; back: Step }
@@ -186,14 +184,10 @@ function backStep(from: Step): Step {
186
184
  return { kind: 'continuity-skills-tree' }
187
185
  case 'continuity-skill-new-visibility':
188
186
  return { kind: 'continuity-skill-new' }
189
- case 'continuity-skill-delete':
187
+ case 'continuity-skill-actions':
190
188
  return { kind: 'continuity-skills-tree' }
191
189
  case 'continuity-skill-delete-confirm':
192
- return { kind: 'continuity-skill-delete' }
193
- case 'continuity-skill-visibility':
194
- return { kind: 'continuity-skills-tree' }
195
- case 'continuity-skill-visibility-pick':
196
- return { kind: 'continuity-skill-visibility' }
190
+ return { kind: 'continuity-skill-actions', relativePath: from.target.relativePath }
197
191
  case 'rebackup-confirm':
198
192
  case 'recovery-refetch-confirm':
199
193
  case 'recovery-refetching':
@@ -57,7 +57,7 @@ export const EditProfileFlow: React.FC<EditProfileFlowProps> = ({
57
57
  const currentName = step.name ?? readIdentityStateString(step.identity.state, 'name')
58
58
  return (
59
59
  <Surface
60
- title="Edit Name, Description, Icon"
60
+ title="Edit Profile"
61
61
  subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={1} />}
62
62
  footer={footerHint(EDIT_NEXT_FOOTER)}
63
63
  >
@@ -112,7 +112,7 @@ export const EditProfileFlow: React.FC<EditProfileFlowProps> = ({
112
112
  const draftDescription = step.description ?? currentDescription
113
113
  return (
114
114
  <Surface
115
- title="Edit Name, Description, Icon"
115
+ title="Edit Profile"
116
116
  subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={2} />}
117
117
  footer={footerHint(EDIT_DESCRIPTION_FOOTER)}
118
118
  >
@@ -145,7 +145,7 @@ const AgentIconStep: React.FC<{
145
145
  if (entryMode) {
146
146
  return (
147
147
  <Surface
148
- title="Edit Name, Description, Icon"
148
+ title="Edit Profile"
149
149
  subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={3} />}
150
150
  footer={footerHint(EDIT_NEXT_FOOTER)}
151
151
  >
@@ -165,7 +165,7 @@ const AgentIconStep: React.FC<{
165
165
 
166
166
  return (
167
167
  <Surface
168
- title="Edit Name, Description, Icon"
168
+ title="Edit Profile"
169
169
  subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={3} />}
170
170
  footer={footerHint('enter select · esc back')}
171
171
  >
@@ -208,7 +208,7 @@ const EditProfileReviewStep: React.FC<{
208
208
  const currentIcon = readIdentityStateString(step.identity.state, 'imageUrl')
209
209
  return (
210
210
  <Surface
211
- title="Edit Name, Description, Icon"
211
+ title="Edit Profile"
212
212
  subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={4} />}
213
213
  footer={footerHint('enter save · esc back')}
214
214
  >
@@ -78,7 +78,7 @@ export async function runRecoveryRefetch(
78
78
  walletSignature: wallet.signature,
79
79
  currentOwnerAddress: getAddress(wallet.account),
80
80
  })
81
- callbacks.onRestoreProgress?.({ phase: 'writing', label: 'restoring SOUL.md, MEMORY.md, and skills.json...' })
81
+ callbacks.onRestoreProgress?.({ phase: 'writing', label: 'restoring SOUL.md, MEMORY.md, and skills...' })
82
82
  const transferSnapshot = transferSnapshotMetadataFromEnvelope(envelope)
83
83
  const refreshedBackup: BackupMetadata = {
84
84
  cid: candidate.backup.cid,
@@ -136,10 +136,10 @@ export async function runRecoveryRefetch(
136
136
  const publicSkillsRestored = await restorePublishedPublicSkills(nextIdentity, apiUrl, candidate.publicDiscovery?.skillsCid)
137
137
  await ensureIdentityMarkdownScaffold(nextIdentity)
138
138
  await syncPublicSkillsManifest(nextIdentity).catch(() => null)
139
- await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'Refetched Latest Snapshot From Chain' }).catch(() => null)
139
+ await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'Refetched Latest Snapshot From Onchain' }).catch(() => null)
140
140
  if (publicSkillsRestored) {
141
141
  const contentHashes = await localContinuitySnapshotContentHashes(nextIdentity)
142
142
  await updatePublishedContinuitySnapshotContentHashes(nextIdentity, candidate.backup.cid, contentHashes).catch(() => null)
143
143
  }
144
- await callbacks.onIdentityComplete(nextIdentity, 'Latest Published Snapshot Restored From Chain', 'update')
144
+ await callbacks.onIdentityComplete(nextIdentity, 'Latest Published Snapshot Restored From Onchain', 'update')
145
145
  }
@@ -28,9 +28,10 @@ interface IdentitySummaryProps {
28
28
  hideHeader?: boolean
29
29
  tokenLinked?: boolean
30
30
  onchainOwner?: string
31
+ compact?: boolean
31
32
  }
32
33
 
33
- export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, config, workingStatus, hideLocalChanges = false, hideHeader = false, tokenLinked = true, onchainOwner }) => {
34
+ export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, config, workingStatus, hideLocalChanges = false, hideHeader = false, tokenLinked = true, onchainOwner, compact = false }) => {
34
35
  if (!identity) {
35
36
  return (
36
37
  <Text color={theme.dim}>No agent yet. Create or load one.</Text>
@@ -59,6 +60,25 @@ export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, conf
59
60
  ? `${tokenValue} · ${displayValue(networkValue)}`
60
61
  : displayValue(tokenValue)
61
62
 
63
+ if (compact) {
64
+ const name = stateName || 'Active Agent'
65
+ const tokenSegment = identity.agentId ? `#${identity.agentId}` : null
66
+ const networkSegment = identity.agentId ? displayValue(networkValue) : null
67
+ const ensSegment = ensStatus.kind === 'linked'
68
+ ? ensStatus.name
69
+ : ensStatus.kind === 'issue'
70
+ ? ensStatus.name
71
+ : null
72
+ return (
73
+ <Text>
74
+ <Text color={theme.accentPeriwinkle} bold>{name}</Text>
75
+ {tokenSegment ? <><Text color={theme.dim}> · </Text><Text color={theme.text}>{tokenSegment}</Text></> : null}
76
+ {networkSegment ? <><Text color={theme.dim}> · </Text><Text color={theme.text}>{networkSegment}</Text></> : null}
77
+ {ensSegment ? <><Text color={theme.dim}> · </Text><Text color={ensStatus.kind === 'issue' ? theme.accentError : theme.accentPeriwinkle}>{ensSegment}</Text></> : null}
78
+ </Text>
79
+ )
80
+ }
81
+
62
82
  return (
63
83
  <Box flexDirection="column">
64
84
  {hideHeader ? null : (
@@ -100,7 +120,7 @@ export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, conf
100
120
  }
101
121
  const pendingCell = {
102
122
  label: 'Pending',
103
- value: <Text color={theme.dim}>local ahead of chain, owner rotates pointer</Text>,
123
+ value: <Text color={theme.dim}>local ahead of onchain, owner rotates pointer</Text>,
104
124
  }
105
125
  return (
106
126
  <>
@@ -112,7 +112,7 @@ export const MenuScreen: React.FC<MenuScreenProps> = ({
112
112
  const options: Array<SelectOption<Action>> = identity
113
113
  ? [
114
114
  { value: 'public-profile', role: 'section', label: 'Public Identity' },
115
- { value: 'public-profile', label: 'Public Profile', hint: 'Identity card and profile fields' },
115
+ { value: 'public-profile', label: 'Public Profile', hint: 'Agent card and profile fields' },
116
116
  { value: 'ens-name', label: 'ENS Name', hint: ensNameHint, disabled: flags?.ensNameDisabled ?? false },
117
117
  { value: 'continuity', role: 'section', label: 'Continuity' },
118
118
  { value: 'continuity', label: 'Soul & Memory', hint: 'Edit SOUL.md and MEMORY.md' },
@@ -197,7 +197,7 @@ function renderReconciliationBanner(r: AgentReconciliation, identity: EthagentId
197
197
  return (
198
198
  <>
199
199
  <Text color={theme.accentError} bold>Agent Unlinked</Text>
200
- <Text color={theme.textSubtle}>{tokenLabel} was transferred. Local SOUL.md, MEMORY.md, skills.json remain. Back them up before this directory is reused.</Text>
200
+ <Text color={theme.textSubtle}>{tokenLabel} was transferred. Local SOUL.md, MEMORY.md, and skills remain. Back them up before this directory is reused.</Text>
201
201
  <Text color={theme.textSubtle}>Use Load Agent or New Agent to re-enable disabled actions.</Text>
202
202
  </>
203
203
  )
@@ -205,7 +205,7 @@ function renderReconciliationBanner(r: AgentReconciliation, identity: EthagentId
205
205
  return (
206
206
  <>
207
207
  <Text color={theme.accentError} bold>Agent Unlinked</Text>
208
- <Text color={theme.textSubtle}>{tokenLabel} left without Prepare Transfer. Back up local SOUL.md, MEMORY.md, skills.json before loading another agent.</Text>
208
+ <Text color={theme.textSubtle}>{tokenLabel} left without Prepare Transfer. Back up local SOUL.md, MEMORY.md, and skills before loading another agent.</Text>
209
209
  <Text color={theme.textSubtle}>For continuity handoff: ask the new holder to return the token, then run Prepare Transfer before re-sending.</Text>
210
210
  <Text color={theme.textSubtle}>Use Load Agent or New Agent to re-enable disabled actions.</Text>
211
211
  </>
@@ -224,7 +224,7 @@ function renderReconciliationBanner(r: AgentReconciliation, identity: EthagentId
224
224
  }
225
225
  const lines: string[] = []
226
226
  if (r.custody === 'mid-flow-uri-pending') lines.push('Advanced setup pending. Open Custody Mode to finish.')
227
- if (r.agentUri === 'local-newer') lines.push('Local state newer than chain. Save Snapshot Now to publish.')
227
+ if (r.agentUri === 'local-newer') lines.push('Local state newer than onchain. Save Snapshot Now to publish.')
228
228
  if (r.agentUri === 'chain-newer') lines.push('Onchain agentURI is newer than local. Refetch Latest.')
229
229
  if (r.vault === 'missing') lines.push('Recorded vault address has no contract at it. Open Custody Mode to redeploy.')
230
230
  if (r.workingTree === 'dirty') lines.push('Local edits pending. Save Snapshot Now to publish.')
@@ -33,7 +33,7 @@ export const UnlinkedIdentityScreen: React.FC<UnlinkedIdentityScreenProps> = ({
33
33
  ]
34
34
  if (onRetry) {
35
35
  options.push({ value: 'retry', role: 'section', label: 'Recheck' })
36
- options.push({ value: 'retry', label: 'Retry Ownership Check', hint: 'Re-query the chain to confirm the current owner', role: 'utility' })
36
+ options.push({ value: 'retry', label: 'Retry Ownership Check', hint: 'Re-query onchain to confirm the current owner', role: 'utility' })
37
37
  }
38
38
 
39
39
  const tokenLabel = agentId ? `Token #${agentId}` : 'Token'
@@ -48,13 +48,13 @@ export const UnlinkedIdentityScreen: React.FC<UnlinkedIdentityScreenProps> = ({
48
48
  <Box flexDirection="column">
49
49
  {transferSnapshot ? (
50
50
  <Text color={theme.textSubtle}>
51
- {tokenLabel} was transferred. Local SOUL.md, MEMORY.md, skills.json remain. Back them up before this directory is reused.
51
+ {tokenLabel} was transferred. Local SOUL.md, MEMORY.md, and skills remain. Back them up before this directory is reused.
52
52
  </Text>
53
53
  ) : (
54
54
  <>
55
55
  <Text color={theme.accentPeriwinkle}>{tokenLabel} left this wallet without Prepare Transfer, so the new holder has no continuity handoff.</Text>
56
56
  <Text color={theme.textSubtle}>
57
- Local SOUL.md, MEMORY.md, skills.json remain. Back them up before this directory is reused.
57
+ Local SOUL.md, MEMORY.md, and skills remain. Back them up before this directory is reused.
58
58
  </Text>
59
59
  </>
60
60
  )}
@@ -57,6 +57,6 @@ export function menuFlagsFromReconciliation(r: AgentReconciliation, perspective:
57
57
  custodyAsterisk: custodyAsterisk && !isOperator,
58
58
  ...(custodyHint ? { custodyHint } : {}),
59
59
  saveSnapshotAsterisk: r.agentUri === 'local-newer',
60
- ...(r.agentUri === 'local-newer' ? { saveSnapshotHint: 'Local newer than chain' } : {}),
60
+ ...(r.agentUri === 'local-newer' ? { saveSnapshotHint: 'Local newer than onchain' } : {}),
61
61
  }
62
62
  }
@@ -208,7 +208,7 @@ export function useIdentityHubContinuity({
208
208
 
209
209
  const deleteSkill = async (relativePath: string): Promise<void> => {
210
210
  await mutateSkillsTree({
211
- backStep: { kind: 'continuity-skill-delete' },
211
+ backStep: { kind: 'continuity-skills-tree' },
212
212
  run: async id => {
213
213
  await deleteSkillEntry(id, relativePath)
214
214
  return `deleted ${relativePath}`
@@ -221,8 +221,8 @@ export function useIdentityHubContinuity({
221
221
  visibility: SkillVisibility,
222
222
  ): Promise<void> => {
223
223
  await mutateSkillsTree({
224
- backStep: { kind: 'continuity-skill-visibility' },
225
- successStep: notice => ({ kind: 'continuity-skill-visibility', notice }),
224
+ backStep: { kind: 'continuity-skill-actions', relativePath },
225
+ successStep: notice => ({ kind: 'continuity-skill-actions', relativePath, notice }),
226
226
  run: async id => {
227
227
  await setSkillVisibilityStorage(id, relativePath, visibility)
228
228
  const display = relativePath.split('/')[0] ?? relativePath
@@ -181,16 +181,15 @@ function serializeOperatorsPointer(pointer: EthagentOperatorsPointer): Record<st
181
181
  export function withEthagentBackupPointer(
182
182
  registration: Record<string, unknown> | null,
183
183
  backup: EthagentBackupPointer,
184
- publicDiscovery?: EthagentPublicDiscoveryPointer,
185
- registrationPointer?: EthagentRegistrationPointer,
186
- ownerAddress?: Address,
184
+ publicDiscovery: EthagentPublicDiscoveryPointer | undefined,
185
+ registrationPointer: EthagentRegistrationPointer | undefined,
186
+ ownerAddress: Address,
187
187
  ): Record<string, unknown> {
188
- const inferredOwnerAddress = ownerAddress ?? backup.agentAddress
189
188
  return withEthagentPointers(registration, {
190
189
  backup,
191
190
  publicDiscovery,
192
191
  registration: registrationPointer,
193
- ...(inferredOwnerAddress ? { ownerAddress: inferredOwnerAddress } : {}),
192
+ ownerAddress,
194
193
  })
195
194
  }
196
195
 
@@ -202,29 +201,26 @@ export function withEthagentPointers(
202
201
  registration?: EthagentRegistrationPointer
203
202
  ensName?: string
204
203
  operators?: EthagentOperatorsPointer
205
- ownerAddress?: Address
204
+ ownerAddress: Address
206
205
  },
207
206
  ): Record<string, unknown> {
208
207
  const next: Record<string, unknown> = registration ? { ...registration } : {}
209
208
  const prior = objectField(next, 'x-ethagent') ?? {}
210
209
  const { backup, publicDiscovery, registration: registrationPointer, operators } = pointers
211
210
  const updatedAt = publicDiscovery?.updatedAt ?? backup?.createdAt
212
- const ownerAddress = pointers.ownerAddress
213
- ? getAddress(pointers.ownerAddress)
214
- : backup?.agentAddress
215
- ? getAddress(backup.agentAddress)
216
- : undefined
211
+ if (!pointers.ownerAddress) {
212
+ throw new Error('withEthagentPointers requires ownerAddress')
213
+ }
214
+ const ownerAddress = getAddress(pointers.ownerAddress)
217
215
  const priorX402 = objectField(prior, 'x402') ?? {}
218
216
  const ext: Record<string, unknown> = {
219
217
  ...prior,
220
218
  version: 1,
221
- ...(ownerAddress ? {
222
- agentAddress: ownerAddress,
223
- x402: {
224
- ...priorX402,
225
- walletAddress: ownerAddress,
226
- },
227
- } : {}),
219
+ agentAddress: ownerAddress,
220
+ x402: {
221
+ ...priorX402,
222
+ walletAddress: ownerAddress,
223
+ },
228
224
  ...(backup ? {
229
225
  backup: {
230
226
  cid: backup.cid,
@@ -252,8 +248,11 @@ export function withEthagentPointers(
252
248
  delete ext.transfer
253
249
  delete ext.handoff
254
250
  next['x-ethagent'] = ext
255
- if (publicDiscovery) {
256
- next.services = withPublicDiscoveryServices(next.services, publicDiscovery, pointers.ensName)
251
+ const agentWalletService = registrationPointer
252
+ ? { name: 'agentWallet' as const, endpoint: `eip155:${registrationPointer.chainId}:${ownerAddress}` }
253
+ : undefined
254
+ if (publicDiscovery || agentWalletService) {
255
+ next.services = withEthagentServices(next.services, publicDiscovery, pointers.ensName, agentWalletService)
257
256
  }
258
257
  if (registrationPointer && registrationPointer.agentId !== undefined) {
259
258
  next.registrations = withRegistrationsArray(next.registrations, registrationPointer)
@@ -272,10 +271,18 @@ function serializeTransferSnapshotMetadata(metadata: TransferSnapshotMetadata):
272
271
  }
273
272
  }
274
273
 
275
- function withPublicDiscoveryServices(input: unknown, publicDiscovery: EthagentPublicDiscoveryPointer, ensName?: string): unknown[] {
274
+ function withEthagentServices(
275
+ input: unknown,
276
+ publicDiscovery: EthagentPublicDiscoveryPointer | undefined,
277
+ ensName: string | undefined,
278
+ agentWallet: { name: 'agentWallet'; endpoint: string } | undefined,
279
+ ): unknown[] {
276
280
  const prior = Array.isArray(input) ? input.filter(item => item && typeof item === 'object') : []
277
281
  const services = prior.filter(item => !isEthagentManagedService(item)) as unknown[]
278
- if (publicDiscovery.agentCardCid) {
282
+ if (agentWallet) {
283
+ pushUniqueService(services, agentWallet)
284
+ }
285
+ if (publicDiscovery?.agentCardCid) {
279
286
  const endpoint = `ipfs://${publicDiscovery.agentCardCid}`
280
287
  pushUniqueService(services, {
281
288
  type: 'a2a',
@@ -284,7 +291,7 @@ function withPublicDiscoveryServices(input: unknown, publicDiscovery: EthagentPu
284
291
  url: endpoint,
285
292
  })
286
293
  }
287
- if (publicDiscovery.skillsCid) {
294
+ if (publicDiscovery?.skillsCid) {
288
295
  const endpoint = `ipfs://${publicDiscovery.skillsCid}`
289
296
  pushUniqueService(services, {
290
297
  type: 'A2A-skills',
@@ -313,6 +320,7 @@ function isEthagentManagedService(item: unknown): boolean {
313
320
  const obj = item as Record<string, unknown>
314
321
  const type = obj.type
315
322
  const name = obj.name
323
+ if (name === 'agentWallet') return true
316
324
  if (name === 'ENS') return true
317
325
  if (type === 'a2a' && (name === undefined || name === 'agent-card')) return true
318
326
  return (type === 'A2A-skills' || type === 'ipfs') && name === 'public-skills'