ethagent 3.2.0 → 3.3.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 (61) hide show
  1. package/README.md +15 -16
  2. package/package.json +1 -1
  3. package/src/identity/continuity/history.ts +8 -8
  4. package/src/identity/continuity/publicSkills.ts +2 -79
  5. package/src/identity/continuity/skills/publicSkillsSync.ts +8 -7
  6. package/src/identity/continuity/snapshots.ts +3 -8
  7. package/src/identity/continuity/storage/defaults.ts +3 -3
  8. package/src/identity/continuity/storage/paths.ts +1 -1
  9. package/src/identity/continuity/storage/scaffold.ts +37 -25
  10. package/src/identity/continuity/storage/status.ts +11 -11
  11. package/src/identity/continuity/storage/types.ts +4 -4
  12. package/src/identity/continuity/storage.ts +4 -4
  13. package/src/identity/ens/agentRecords.ts +61 -45
  14. package/src/identity/ens/ensAutomation/read.ts +7 -10
  15. package/src/identity/ens/ensAutomation/setup.ts +10 -16
  16. package/src/identity/ens/ensAutomation/types.ts +0 -1
  17. package/src/identity/ens/ensAutomation.ts +1 -0
  18. package/src/identity/ens/ensLookup/records.ts +1 -1
  19. package/src/identity/ens/ensLookup.ts +1 -1
  20. package/src/identity/ens/erc7930.ts +48 -0
  21. package/src/identity/hub/OperationalRoutes.tsx +4 -2
  22. package/src/identity/hub/Routes.tsx +1 -1
  23. package/src/identity/hub/continuity/effects.ts +17 -39
  24. package/src/identity/hub/continuity/skills/NewSkillVisibilityScreen.tsx +2 -2
  25. package/src/identity/hub/continuity/skills/SkillActionsScreen.tsx +2 -2
  26. package/src/identity/hub/continuity/state.ts +1 -1
  27. package/src/identity/hub/continuity/vault.ts +16 -50
  28. package/src/identity/hub/create/effects.ts +12 -16
  29. package/src/identity/hub/ens/EnsEditAdvancedScreens.tsx +0 -76
  30. package/src/identity/hub/ens/EnsEditFlow.tsx +28 -8
  31. package/src/identity/hub/ens/EnsEditMaintenanceScreens.tsx +3 -7
  32. package/src/identity/hub/ens/EnsEditReviewScreens.tsx +14 -18
  33. package/src/identity/hub/ens/EnsEditShared.tsx +4 -6
  34. package/src/identity/hub/ens/EnsEditSimpleScreens.tsx +2 -2
  35. package/src/identity/hub/ens/EnsFlow.tsx +0 -3
  36. package/src/identity/hub/ens/editCopy.ts +7 -15
  37. package/src/identity/hub/ens/transactions.ts +67 -18
  38. package/src/identity/hub/ens/types.ts +0 -2
  39. package/src/identity/hub/profile/EditProfileFlow.tsx +0 -3
  40. package/src/identity/hub/profile/effects.ts +15 -30
  41. package/src/identity/hub/profile/identity.ts +2 -4
  42. package/src/identity/hub/profile/operatorSave.ts +10 -30
  43. package/src/identity/hub/restore/RestoreFlow.tsx +9 -9
  44. package/src/identity/hub/restore/apply.ts +7 -8
  45. package/src/identity/hub/restore/helpers.ts +3 -3
  46. package/src/identity/hub/restore/recovery.ts +9 -10
  47. package/src/identity/hub/restore/resolve.ts +11 -9
  48. package/src/identity/hub/shared/components/MenuScreen.tsx +3 -3
  49. package/src/identity/hub/shared/components/UnlinkedIdentityScreen.tsx +2 -2
  50. package/src/identity/hub/shared/effects/sync.ts +1 -1
  51. package/src/identity/hub/transfer/TokenTransferScreens.tsx +1 -1
  52. package/src/identity/hub/transfer/effects.ts +10 -31
  53. package/src/identity/hub/useIdentityHubContinuity.ts +12 -12
  54. package/src/identity/hub/useIdentityHubController.ts +12 -3
  55. package/src/identity/registry/erc8004/metadata.ts +10 -27
  56. package/src/identity/registry/erc8004/types.ts +0 -1
  57. package/src/storage/config.ts +1 -2
  58. package/src/storage/identity.ts +3 -3
  59. package/src/storage/rewind.ts +1 -1
  60. package/src/tools/privateContinuityEditTool.ts +4 -4
  61. package/src/utils/withRetry.ts +2 -2
@@ -7,7 +7,7 @@ import {
7
7
  type Erc8004RegistryConfig,
8
8
  } from '../../registry/erc8004.js'
9
9
  import { parseAgentTokenReference, readEthagentTextRecords } from '../../ens/ensLookup.js'
10
- import { AGENT_RECORD_KEYS } from '../../ens/agentRecords.js'
10
+ import { AGENT_TOKEN_RECORD_KEY } from '../../ens/agentRecords.js'
11
11
 
12
12
  const ETH_NAME_PATTERN = /^([a-z0-9-]+\.)+eth$/i
13
13
 
@@ -22,26 +22,28 @@ export async function resolveAgentEnsToCandidate(
22
22
  const trimmed = ensName.trim()
23
23
  if (!trimmed) return { ok: false, message: 'Enter an agent ENS name (e.g. agent.example.eth).' }
24
24
  if (!ETH_NAME_PATTERN.test(trimmed)) return { ok: false, message: 'Enter a valid .eth name.' }
25
+
25
26
  let records: Record<string, string>
26
27
  try {
27
- records = await readEthagentTextRecords(trimmed, [AGENT_RECORD_KEYS.token])
28
+ records = await readEthagentTextRecords(trimmed, [AGENT_TOKEN_RECORD_KEY])
28
29
  } catch (err: unknown) {
29
- return { ok: false, message: `Could not reach Ethereum mainnet to resolve ${trimmed}: ${err instanceof Error ? err.message : String(err)}` }
30
+ return { ok: false, message: `Could not reach Ethereum Mainnet to resolve ${trimmed}: ${err instanceof Error ? err.message : String(err)}` }
30
31
  }
31
- const tokenValue = records[AGENT_RECORD_KEYS.token]
32
- if (!tokenValue) return { ok: false, message: `${trimmed} has no org.ethagent.token record.` }
32
+ const tokenValue = records[AGENT_TOKEN_RECORD_KEY]
33
+ if (!tokenValue) return { ok: false, message: `${trimmed} has no agent discovery record. Use token-ID restore instead, or re-link this ENS name to refresh its records.` }
33
34
  const tokenRef = parseAgentTokenReference(tokenValue)
34
- if (!tokenRef) return { ok: false, message: `${trimmed}'s org.ethagent.token record is not a valid eip155 reference.` }
35
+ if (!tokenRef) return { ok: false, message: `${trimmed}'s agent discovery record is not a valid eip155 reference.` }
35
36
  if (tokenRef.chainId !== registry.chainId) {
36
37
  return { ok: false, message: `${trimmed}'s agent token is onchain ${tokenRef.chainId}, not the network you selected.` }
37
38
  }
38
39
  const finalRegistry: Erc8004RegistryConfig = registry.identityRegistryAddress.toLowerCase() === tokenRef.identityRegistryAddress.toLowerCase()
39
40
  ? registry
40
41
  : { ...registry, identityRegistryAddress: tokenRef.identityRegistryAddress }
41
- let owner: Address
42
+
43
+ let onchainOwner: Address
42
44
  try {
43
45
  const publicClient = createErc8004PublicClient(finalRegistry)
44
- owner = await publicClient.readContract({
46
+ onchainOwner = await publicClient.readContract({
45
47
  address: finalRegistry.identityRegistryAddress,
46
48
  abi: [{ inputs: [{ name: 'tokenId', type: 'uint256' }], name: 'ownerOf', outputs: [{ name: '', type: 'address' }], stateMutability: 'view', type: 'function' }] as const,
47
49
  functionName: 'ownerOf',
@@ -53,7 +55,7 @@ export async function resolveAgentEnsToCandidate(
53
55
  try {
54
56
  const candidate = await discoverOwnedAgentBackupByTokenId({
55
57
  ...finalRegistry,
56
- ownerHandle: owner,
58
+ ownerHandle: onchainOwner,
57
59
  ipfsApiUrl: DEFAULT_IPFS_API_URL,
58
60
  tokenId: tokenRef.agentId,
59
61
  })
@@ -124,7 +124,7 @@ export const MenuScreen: React.FC<MenuScreenProps> = ({
124
124
  { value: 'prepare-transfer', label: 'Prepare Transfer', hint: prepareTransferHint, disabled: flags?.prepareTransferDisabled ?? false },
125
125
  { value: 'identity-values', role: 'section', label: 'Token' },
126
126
  { value: 'identity-values', label: 'Token Values', hint: tokenValuesHint },
127
- { value: 'load', label: 'Load Agent', hint: 'Refresh or load another agent' },
127
+ { value: 'load', label: 'Switch Agent', hint: 'Switch agent or wallet' },
128
128
  { value: 'create', label: 'New Agent', hint: 'Mint another agent' },
129
129
  { value: 'storage', label: 'IPFS Storage', hint: 'Publishing credentials' },
130
130
  { value: 'cancel', role: 'section', label: 'Exit' },
@@ -198,7 +198,7 @@ function renderReconciliationBanner(r: AgentReconciliation, identity: EthagentId
198
198
  <>
199
199
  <Text color={theme.accentError} bold>Agent Unlinked</Text>
200
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
- <Text color={theme.textSubtle}>Use Load Agent or New Agent to re-enable disabled actions.</Text>
201
+ <Text color={theme.textSubtle}>Use Switch Agent or New Agent to re-enable disabled actions.</Text>
202
202
  </>
203
203
  )
204
204
  }
@@ -207,7 +207,7 @@ function renderReconciliationBanner(r: AgentReconciliation, identity: EthagentId
207
207
  <Text color={theme.accentError} bold>Agent Unlinked</Text>
208
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
- <Text color={theme.textSubtle}>Use Load Agent or New Agent to re-enable disabled actions.</Text>
210
+ <Text color={theme.textSubtle}>Use Switch Agent or New Agent to re-enable disabled actions.</Text>
211
211
  </>
212
212
  )
213
213
  }
@@ -26,8 +26,8 @@ export const UnlinkedIdentityScreen: React.FC<UnlinkedIdentityScreenProps> = ({
26
26
  onCancel,
27
27
  }) => {
28
28
  const options: Array<{ value: Action; label: string; hint?: string; role?: 'section' | 'utility' }> = [
29
- { value: 'load-agent', role: 'section', label: 'Load Agent' },
30
- { value: 'load-agent', label: 'Load Agent', hint: 'Reconnect this token by signing with the current owner wallet, or load a different one' },
29
+ { value: 'load-agent', role: 'section', label: 'Switch Agent' },
30
+ { value: 'load-agent', label: 'Switch Agent', hint: 'Reconnect this token by signing with the current owner wallet, or switch to a different one' },
31
31
  { value: 'open-menu', role: 'section', label: 'Identity Hub' },
32
32
  { value: 'open-menu', label: 'Open Identity Hub', hint: 'Browse local identity, continuity files, and settings without reconnecting' },
33
33
  ]
@@ -149,7 +149,7 @@ export async function markCurrentContinuityFilesPublished(
149
149
  identity: EthagentIdentity,
150
150
  publishedSources?: {
151
151
  privateFiles: ContinuityFiles
152
- publicSkills: string
152
+ agentCard: string
153
153
  skills: ContinuitySkillsTree
154
154
  },
155
155
  ): Promise<void> {
@@ -192,7 +192,7 @@ export const TokenTransferReadyScreen: React.FC<TokenTransferReadyScreenProps> =
192
192
  <Box marginTop={1} flexDirection="column">
193
193
  <Text color={theme.textSubtle}>Use this process for every ERC-8004 token transfer.</Text>
194
194
  <Text color={theme.textSubtle}>Both sender and receiver signatures can decrypt this snapshot.</Text>
195
- <Text color={theme.textSubtle}>After transfer, use Load Agent with the receiver wallet.</Text>
195
+ <Text color={theme.textSubtle}>After transfer, use Switch Agent with the receiver wallet.</Text>
196
196
  <Text color={theme.textSubtle}>{APPROVAL_GUARDRAIL}</Text>
197
197
  </Box>
198
198
  <Box marginTop={1}>
@@ -11,18 +11,10 @@ import {
11
11
  continuityVaultStatus,
12
12
  prepareSyncedSkillsTree,
13
13
  readContinuityFiles,
14
- readPublicSkillsFile,
15
- writePublicSkillsFile,
14
+ writeAgentCardFile,
16
15
  } from '../../continuity/storage.js'
17
16
  import {
18
- appendPublicSkillEntries,
19
- createAgentCard,
20
- defaultPublicSkillsProfile,
21
- serializeAgentCard,
22
- } from '../../continuity/publicSkills.js'
23
- import {
24
- derivePublicSkillEntries,
25
- syncPublicSkillsManifest,
17
+ syncAgentCardManifest,
26
18
  } from '../../continuity/skills/publicSkillsSync.js'
27
19
  import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
28
20
  import { addToIpfs, DEFAULT_IPFS_API_URL, isPinataUploadUrl } from '../../storage/ipfs.js'
@@ -47,7 +39,7 @@ import { tokenTransferProgressForPhase } from './progress.js'
47
39
  import { assertTokenNotInVault } from '../custody/preflight.js'
48
40
 
49
41
  type BackupMetadata = NonNullable<EthagentIdentity['backup']>
50
- type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
42
+ type AgentCardMetadata = NonNullable<EthagentIdentity['agentCard']>
51
43
 
52
44
  type TokenTransferResult = {
53
45
  identity: EthagentIdentity
@@ -167,20 +159,8 @@ export async function runTokenTransferSigning(
167
159
  }
168
160
  const nextIdentityForFiles: EthagentIdentity = { ...step.identity, state }
169
161
  const continuityFiles = await readContinuityFiles(nextIdentityForFiles)
170
- const publicSkillsJson = await syncPublicSkillsManifest(nextIdentityForFiles)
171
- const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
172
- assertVerifiedPin(publicSkillsPin)
173
- const publicSkillEntries = await derivePublicSkillEntries(nextIdentityForFiles)
174
- const augmentedPublicProfile = appendPublicSkillEntries(
175
- defaultPublicSkillsProfile(nextIdentityForFiles),
176
- publicSkillEntries,
177
- )
178
- const agentCardPin = await addToIpfs(
179
- DEFAULT_IPFS_API_URL,
180
- serializeAgentCard(createAgentCard(augmentedPublicProfile)),
181
- fetch,
182
- { pinataJwt: step.pinataJwt },
183
- )
162
+ const agentCardJson = await syncAgentCardManifest(nextIdentityForFiles)
163
+ const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, agentCardJson, fetch, { pinataJwt: step.pinataJwt })
184
164
  assertVerifiedPin(agentCardPin)
185
165
  const skillsTree = await prepareSyncedSkillsTree(nextIdentityForFiles)
186
166
  const envelope = createTransferContinuitySnapshotEnvelope({
@@ -215,9 +195,8 @@ export async function runTokenTransferSigning(
215
195
  agentId: transferAgentId,
216
196
  ...(transferSnapshot ? { transferSnapshot } : {}),
217
197
  }
218
- const publicSkills: PublicSkillsMetadata = {
219
- cid: publicSkillsPin.cid,
220
- agentCardCid: agentCardPin.cid,
198
+ const agentCard: AgentCardMetadata = {
199
+ cid: agentCardPin.cid,
221
200
  updatedAt: envelope.createdAt,
222
201
  status: 'pinned',
223
202
  }
@@ -232,7 +211,7 @@ export async function runTokenTransferSigning(
232
211
  ...(uploadedImageUri ? { image: uploadedImageUri } : {}),
233
212
  }, {
234
213
  backup: { cid: snapshotCid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt, ...(transferSnapshot ? { transferSnapshot } : {}) },
235
- publicDiscovery: { skillsCid: publicSkills.cid, agentCardCid: publicSkills.agentCardCid, updatedAt: publicSkills.updatedAt },
214
+ publicDiscovery: { agentCardCid: agentCard.cid, updatedAt: agentCard.updatedAt },
236
215
  registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress, agentId: transferAgentId },
237
216
  ensName: nextEnsName,
238
217
  operators: operatorsPointerFromState(state, nextEnsName),
@@ -273,10 +252,10 @@ export async function runTokenTransferSigning(
273
252
  agentUri,
274
253
  metadataCid,
275
254
  backup: { ...backup, metadataCid, agentUri, txHash: tx.txHash },
276
- publicSkills,
255
+ agentCard,
277
256
  state,
278
257
  }
279
- await writePublicSkillsFile(nextIdentity, publicSkillsJson)
258
+ await writeAgentCardFile(nextIdentity, agentCardJson)
280
259
  await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'published transfer snapshot' }).catch(() => null)
281
260
  callbacks.onTokenTransferProgress?.(null)
282
261
  return {
@@ -4,7 +4,7 @@ import { catFromIpfs, DEFAULT_IPFS_API_URL } from '../storage/ipfs.js'
4
4
  import {
5
5
  continuityVaultStatus,
6
6
  continuityWorkingTreeStatus,
7
- ensurePublicSkillsFile,
7
+ ensureAgentCardFile,
8
8
  type ContinuityWorkingTreeStatus,
9
9
  } from '../continuity/storage.js'
10
10
  import { openFileInEditor, openInFileManager } from '../continuity/editor.js'
@@ -17,7 +17,7 @@ import {
17
17
  setSkillVisibility as setSkillVisibilityStorage,
18
18
  } from '../continuity/skills/loadSkills.js'
19
19
  import type { SkillVisibility } from '../continuity/skills/types.js'
20
- import { syncPublicSkillsManifest } from '../continuity/skills/publicSkillsSync.js'
20
+ import { syncAgentCardManifest } from '../continuity/skills/publicSkillsSync.js'
21
21
  import { continuityVaultRef } from '../continuity/storage.js'
22
22
  import type { Step } from './identityHubReducer.js'
23
23
 
@@ -109,7 +109,7 @@ export function useIdentityHubContinuity({
109
109
  const id = await requireReadyVault()
110
110
  const notice = await args.run(id)
111
111
  invalidateSkillsCache(id)
112
- await syncPublicSkillsManifest(id)
112
+ await syncAgentCardManifest(id)
113
113
  const next = args.successStep
114
114
  ? args.successStep(notice)
115
115
  : { kind: 'continuity-skills-tree' as const, notice }
@@ -126,12 +126,12 @@ export function useIdentityHubContinuity({
126
126
  : 'continuity-private'
127
127
  try {
128
128
  if (kind === 'skills') {
129
- await ensurePublicSkillsFile(identity, {
130
- fallback: () => readPublishedPublicSkills(identity),
129
+ await ensureAgentCardFile(identity, {
130
+ fallback: () => readPublishedAgentCard(identity),
131
131
  })
132
132
  }
133
133
  const ref = continuityVaultRef(identity)
134
- const file = kind === 'soul' ? ref.soulPath : kind === 'memory' ? ref.memoryPath : ref.publicSkillsPath
134
+ const file = kind === 'soul' ? ref.soulPath : kind === 'memory' ? ref.memoryPath : ref.agentCardPath
135
135
  const result = await openFileInEditor(file)
136
136
  if (result.ok) {
137
137
  setStep({ kind: returnKind, editorOpened: true })
@@ -150,10 +150,10 @@ export function useIdentityHubContinuity({
150
150
  const result = await openFileInEditor(skill.absolutePath)
151
151
  invalidateSkillsCache(identity)
152
152
  try {
153
- await syncPublicSkillsManifest(identity)
153
+ await syncAgentCardManifest(identity)
154
154
  } catch (syncErr: unknown) {
155
155
  const failPrefix = result.ok ? '' : `open failed: ${result.error}; `
156
- setStep({ kind: 'continuity-skills-tree', notice: `${failPrefix}public manifest sync failed: ${(syncErr as Error).message}`, editorOpened: result.ok })
156
+ setStep({ kind: 'continuity-skills-tree', notice: `${failPrefix}agent card update failed: ${(syncErr as Error).message}`, editorOpened: result.ok })
157
157
  return
158
158
  }
159
159
  if (result.ok) {
@@ -194,7 +194,7 @@ export function useIdentityHubContinuity({
194
194
  const id = await requireReadyVault()
195
195
  const created = await createSkillFile(id, { name: normalizedName, visibility })
196
196
  invalidateSkillsCache(id)
197
- await syncPublicSkillsManifest(id)
197
+ await syncAgentCardManifest(id)
198
198
  const result = await openFileInEditor(created.absolutePath)
199
199
  if (result.ok) {
200
200
  setStep({ kind: 'continuity-skills-tree', editorOpened: true })
@@ -248,9 +248,9 @@ export function sanitizeSkillSegment(value: string): string {
248
248
  return value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 60)
249
249
  }
250
250
 
251
- async function readPublishedPublicSkills(identity: EthagentIdentity): Promise<string> {
252
- const cid = identity.publicSkills?.cid
253
- if (!cid) throw new Error('No saved public skills CID')
251
+ async function readPublishedAgentCard(identity: EthagentIdentity): Promise<string> {
252
+ const cid = identity.agentCard?.cid
253
+ if (!cid) throw new Error('No saved Agent Card CID')
254
254
  return new TextDecoder().decode(await catFromIpfs(
255
255
  identity.backup?.ipfsApiUrl ?? DEFAULT_IPFS_API_URL,
256
256
  cid,
@@ -240,9 +240,14 @@ export function useIdentityHubController({
240
240
  handleStepError(new Error('no agent registry configured for this identity'), backStep)
241
241
  return
242
242
  }
243
+ const isAdvanced = readCustodyMode(identity.state as Record<string, unknown> | undefined) === 'advanced'
243
244
  const vaultAddress = options?.useVault === false
244
245
  ? undefined
245
- : options?.vaultAddress ?? resolveVaultAddress(identity, config?.erc8004?.operatorVaults)
246
+ : options?.vaultAddress != null
247
+ ? options.vaultAddress
248
+ : isAdvanced
249
+ ? resolveVaultAddress(identity, config?.erc8004?.operatorVaults)
250
+ : undefined
246
251
  ;(async () => {
247
252
  const role: 'token-holder' | 'vault-level-owner' = vaultAddress ? 'vault-level-owner' : 'token-holder'
248
253
  const allowed = await guardOwnership(identity, registry, role, backStep)
@@ -260,8 +265,12 @@ export function useIdentityHubController({
260
265
  return
261
266
  }
262
267
  ;(async () => {
263
- const vaultAddress = resolveVaultAddress(identity, config?.erc8004?.operatorVaults)
264
- const allowed = await guardOwnership(identity, registry, 'vault-level-owner', backStep)
268
+ const isAdvanced = readCustodyMode(identity.state as Record<string, unknown> | undefined) === 'advanced'
269
+ const vaultAddress = isAdvanced
270
+ ? resolveVaultAddress(identity, config?.erc8004?.operatorVaults)
271
+ : undefined
272
+ const role: 'token-holder' | 'vault-level-owner' = vaultAddress ? 'vault-level-owner' : 'token-holder'
273
+ const allowed = await guardOwnership(identity, registry, role, backStep)
265
274
  if (!allowed) return
266
275
  runPublicProfilePreflight(identity, registry, callbacks, profileUpdates, backStep, vaultAddress)
267
276
  .catch((err: unknown) => handleStepError(err, backStep))
@@ -62,16 +62,15 @@ function parseTransferSnapshotMetadata(input: Record<string, unknown> | null): T
62
62
  export function parseEthagentPublicDiscoveryPointer(registration: Record<string, unknown> | null): EthagentPublicDiscoveryPointer | null {
63
63
  if (!registration) return null
64
64
  const ext = objectField(registration, 'x-ethagent') ?? objectField(registration, 'ethagent')
65
- const publicSkills = ext ? objectField(ext, 'publicSkills') : null
66
65
  const agentCard = ext ? objectField(ext, 'agentCard') : null
67
- const skillsCid = publicSkills ? stringField(publicSkills, 'cid') : undefined
68
- const agentCardCid = agentCard ? stringField(agentCard, 'cid') : undefined
69
- const updatedAt = (publicSkills ? stringField(publicSkills, 'updatedAt') : undefined)
70
- ?? (agentCard ? stringField(agentCard, 'updatedAt') : undefined)
71
- if (!skillsCid && !agentCardCid) return null
66
+ const legacyPublicSkills = ext ? objectField(ext, 'publicSkills') : null
67
+ const agentCardCid = (agentCard ? stringField(agentCard, 'cid') : undefined)
68
+ ?? (legacyPublicSkills ? stringField(legacyPublicSkills, 'cid') : undefined)
69
+ const updatedAt = (agentCard ? stringField(agentCard, 'updatedAt') : undefined)
70
+ ?? (legacyPublicSkills ? stringField(legacyPublicSkills, 'updatedAt') : undefined)
71
+ if (!agentCardCid) return null
72
72
  return {
73
- ...(skillsCid ? { skillsCid } : {}),
74
- ...(agentCardCid ? { agentCardCid } : {}),
73
+ agentCardCid,
75
74
  ...(updatedAt ? { updatedAt } : {}),
76
75
  }
77
76
  }
@@ -166,7 +165,6 @@ function serializeOperatorsPointer(pointer: EthagentOperatorsPointer): Record<st
166
165
  return {
167
166
  approvedOperatorWallets: pointer.approvedOperatorWallets.map(record => ({
168
167
  address: getAddress(record.address),
169
- ...(record.challenge ? { challenge: record.challenge } : {}),
170
168
  ...(record.verifiedAt ? { verifiedAt: record.verifiedAt } : {}),
171
169
  ...(record.restoreAccessKey ? { restoreAccessKey: serializeRestoreAccessKey(record.restoreAccessKey) } : {}),
172
170
  })),
@@ -229,13 +227,6 @@ export function withEthagentPointers(
229
227
  ...(backup.transferSnapshot ? { transferSnapshot: serializeTransferSnapshotMetadata(backup.transferSnapshot) } : {}),
230
228
  },
231
229
  } : {}),
232
- ...(publicDiscovery?.skillsCid ? {
233
- publicSkills: {
234
- cid: publicDiscovery.skillsCid,
235
- format: 'application/json',
236
- ...(updatedAt ? { updatedAt } : {}),
237
- },
238
- } : {}),
239
230
  ...(publicDiscovery?.agentCardCid ? {
240
231
  agentCard: {
241
232
  cid: publicDiscovery.agentCardCid,
@@ -285,17 +276,9 @@ function withEthagentServices(
285
276
  if (publicDiscovery?.agentCardCid) {
286
277
  const endpoint = `ipfs://${publicDiscovery.agentCardCid}`
287
278
  pushUniqueService(services, {
288
- type: 'a2a',
279
+ type: 'A2A',
289
280
  name: 'agent-card',
290
- endpoint,
291
- url: endpoint,
292
- })
293
- }
294
- if (publicDiscovery?.skillsCid) {
295
- const endpoint = `ipfs://${publicDiscovery.skillsCid}`
296
- pushUniqueService(services, {
297
- type: 'A2A-skills',
298
- name: 'public-skills',
281
+ version: '0.3.0',
299
282
  endpoint,
300
283
  url: endpoint,
301
284
  })
@@ -322,7 +305,7 @@ function isEthagentManagedService(item: unknown): boolean {
322
305
  const name = obj.name
323
306
  if (name === 'agentWallet') return true
324
307
  if (name === 'ENS') return true
325
- if (type === 'a2a' && (name === undefined || name === 'agent-card')) return true
308
+ if ((type === 'A2A' || type === 'a2a') && (name === undefined || name === 'agent-card')) return true
326
309
  return (type === 'A2A-skills' || type === 'ipfs') && name === 'public-skills'
327
310
  }
328
311
 
@@ -21,7 +21,6 @@ export type EthagentBackupPointer = {
21
21
  }
22
22
 
23
23
  export type EthagentPublicDiscoveryPointer = {
24
- skillsCid?: string
25
24
  agentCardCid?: string
26
25
  updatedAt?: string
27
26
  }
@@ -51,9 +51,8 @@ const IdentitySchema = z.object({
51
51
  createdAt: z.string(),
52
52
  })).optional(),
53
53
  }).optional(),
54
- publicSkills: z.object({
54
+ agentCard: z.object({
55
55
  cid: z.string().min(1).optional(),
56
- agentCardCid: z.string().min(1).optional(),
57
56
  updatedAt: z.string().optional(),
58
57
  status: z.enum(['pinned', 'failed', 'unknown']).optional(),
59
58
  }).optional(),
@@ -18,7 +18,7 @@ export type IdentityStatus = {
18
18
  createdAt: string
19
19
  backend: KeyBackend | 'browser-wallet'
20
20
  backup?: EthagentIdentity['backup']
21
- publicSkills?: EthagentIdentity['publicSkills']
21
+ agentCard?: EthagentIdentity['agentCard']
22
22
  source?: EthagentIdentity['source']
23
23
  agentId?: string
24
24
  chainId?: number
@@ -33,7 +33,7 @@ export async function getIdentityStatus(config?: EthagentConfig): Promise<Identi
33
33
  createdAt: resolved.identity.createdAt,
34
34
  backend: 'browser-wallet',
35
35
  backup: resolved.identity.backup,
36
- publicSkills: resolved.identity.publicSkills,
36
+ agentCard: resolved.identity.agentCard,
37
37
  source: resolved.identity.source,
38
38
  agentId: resolved.identity.agentId,
39
39
  chainId: resolved.identity.chainId,
@@ -47,7 +47,7 @@ export async function getIdentityStatus(config?: EthagentConfig): Promise<Identi
47
47
  createdAt: resolved.identity.createdAt,
48
48
  backend,
49
49
  backup: resolved.identity.backup,
50
- publicSkills: resolved.identity.publicSkills,
50
+ agentCard: resolved.identity.agentCard,
51
51
  source: resolved.identity.source,
52
52
  chainId: resolved.identity.chainId,
53
53
  }
@@ -257,7 +257,7 @@ function isIdentityMarkdownSnapshot(snapshot: RewindSnapshot): boolean {
257
257
  )
258
258
  }
259
259
 
260
- const IDENTITY_MARKDOWN_FILES = new Set(['soul.md', 'memory.md', 'skills.json'])
260
+ const IDENTITY_MARKDOWN_FILES = new Set(['soul.md', 'memory.md', 'agent-card.json'])
261
261
 
262
262
  function normalizeSnippet(input?: string): string {
263
263
  const normalized = (input ?? '').replace(/\s+/g, ' ').trim()
@@ -4,7 +4,7 @@ import {
4
4
  writePreparedPrivateContinuityEdit,
5
5
  } from '../identity/continuity/privateEdit.js'
6
6
  import { recordPrivateContinuityHistorySnapshot } from '../identity/continuity/history.js'
7
- import { readContinuityFiles, readPublicSkillsFile } from '../identity/continuity/storage.js'
7
+ import { readContinuityFiles, readAgentCardFile } from '../identity/continuity/storage.js'
8
8
  import type { Tool } from './contracts.js'
9
9
  import { formatFileChangeResult } from './fileDiff.js'
10
10
 
@@ -120,9 +120,9 @@ export const privateContinuityEditTool: Tool<typeof schema> = {
120
120
  },
121
121
  async execute(input, context) {
122
122
  const prepared = await preparePrivateContinuityEdit(input, context.config)
123
- const [previousFiles, previousPublicSkills] = await Promise.all([
123
+ const [previousFiles, previousAgentCard] = await Promise.all([
124
124
  readContinuityFiles(prepared.identity),
125
- readPublicSkillsFile(prepared.identity),
125
+ readAgentCardFile(prepared.identity),
126
126
  ])
127
127
  await recordPrivateContinuityHistorySnapshot({
128
128
  identity: prepared.identity,
@@ -131,7 +131,7 @@ export const privateContinuityEditTool: Tool<typeof schema> = {
131
131
  existedBefore: prepared.existedBefore,
132
132
  previousContent: prepared.previousContent,
133
133
  previousFiles,
134
- previousPublicSkills,
134
+ previousAgentCard,
135
135
  changeSummary: prepared.changeSummary,
136
136
  sessionId: context.checkpoint?.sessionId,
137
137
  turnId: context.checkpoint?.turnId,
@@ -220,7 +220,7 @@ export async function fetchWithRetry(
220
220
  && options.parseRetryHintFromBody
221
221
  ) {
222
222
  let bodyText = ''
223
- try { bodyText = await response.text() } catch { /* ignore */ }
223
+ try { bodyText = await response.text() } catch {}
224
224
  bufferedResponse = new Response(bodyText, {
225
225
  status: response.status,
226
226
  statusText: response.statusText,
@@ -240,7 +240,7 @@ export async function fetchWithRetry(
240
240
  if (!classification.retryable || attempt > policy.maxRetries) return bufferedResponse ?? response
241
241
 
242
242
  if (!bufferedResponse) {
243
- try { await response.body?.cancel() } catch { /* ignore */ }
243
+ try { await response.body?.cancel() } catch {}
244
244
  }
245
245
  const delayMs = computeBackoffMs(
246
246
  attempt,