ethagent 3.1.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +15 -16
  2. package/package.json +1 -1
  3. package/src/chat/ChatScreen.tsx +25 -2
  4. package/src/chat/chatTurnOrchestrator.ts +17 -0
  5. package/src/identity/continuity/history.ts +8 -8
  6. package/src/identity/continuity/publicSkills.ts +3 -80
  7. package/src/identity/continuity/skills/frontmatter.ts +4 -1
  8. package/src/identity/continuity/skills/loadSkills.ts +73 -5
  9. package/src/identity/continuity/skills/publicSkillsSync.ts +9 -8
  10. package/src/identity/continuity/skills/scaffold.ts +1 -1
  11. package/src/identity/continuity/skills/types.ts +1 -1
  12. package/src/identity/continuity/snapshots.ts +3 -8
  13. package/src/identity/continuity/storage/defaults.ts +3 -3
  14. package/src/identity/continuity/storage/paths.ts +1 -1
  15. package/src/identity/continuity/storage/scaffold.ts +37 -25
  16. package/src/identity/continuity/storage/status.ts +11 -11
  17. package/src/identity/continuity/storage/types.ts +4 -4
  18. package/src/identity/continuity/storage.ts +4 -4
  19. package/src/identity/ens/agentRecords.ts +61 -45
  20. package/src/identity/ens/ensAutomation/read.ts +7 -10
  21. package/src/identity/ens/ensAutomation/setup.ts +10 -16
  22. package/src/identity/ens/ensAutomation/types.ts +0 -1
  23. package/src/identity/ens/ensAutomation.ts +1 -0
  24. package/src/identity/ens/ensLookup/records.ts +1 -1
  25. package/src/identity/ens/ensLookup.ts +1 -1
  26. package/src/identity/ens/erc7930.ts +48 -0
  27. package/src/identity/hub/OperationalRoutes.tsx +4 -1
  28. package/src/identity/hub/continuity/effects.ts +17 -39
  29. package/src/identity/hub/continuity/skills/NewSkillVisibilityScreen.tsx +3 -4
  30. package/src/identity/hub/continuity/skills/SkillActionsScreen.tsx +3 -4
  31. package/src/identity/hub/continuity/skills/SkillsTreeScreen.tsx +2 -1
  32. package/src/identity/hub/continuity/state.ts +1 -1
  33. package/src/identity/hub/continuity/vault.ts +16 -50
  34. package/src/identity/hub/create/effects.ts +12 -16
  35. package/src/identity/hub/ens/EnsEditFlow.tsx +19 -5
  36. package/src/identity/hub/ens/EnsEditReviewScreens.tsx +11 -11
  37. package/src/identity/hub/ens/EnsEditShared.tsx +2 -6
  38. package/src/identity/hub/ens/editCopy.ts +1 -3
  39. package/src/identity/hub/ens/transactions.ts +67 -18
  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/models/llamacpp.ts +61 -1
  58. package/src/models/llamacppPreflight.ts +5 -1
  59. package/src/runtime/compaction.ts +11 -5
  60. package/src/storage/config.ts +1 -2
  61. package/src/storage/identity.ts +3 -3
  62. package/src/storage/rewind.ts +1 -1
  63. package/src/tools/privateContinuityEditTool.ts +4 -4
  64. package/src/utils/messages.ts +1 -1
  65. package/src/utils/withRetry.ts +2 -2
@@ -6,17 +6,11 @@ import {
6
6
  prepareSyncedIdentityMarkdownScaffold,
7
7
  prepareSyncedSkillsTree,
8
8
  readContinuityFiles,
9
- readPublicSkillsFile,
10
9
  writeIdentityMarkdownScaffold,
11
10
  type IdentityMarkdownScaffold,
12
11
  } from '../../continuity/storage.js'
13
12
  import {
14
- appendPublicSkillEntries,
15
- defaultPublicSkillsProfile as basePublicSkillsProfile,
16
- } from '../../continuity/publicSkills.js'
17
- import {
18
- derivePublicSkillEntries,
19
- syncPublicSkillsManifest,
13
+ syncAgentCardManifest,
20
14
  } from '../../continuity/skills/publicSkillsSync.js'
21
15
  import {
22
16
  createWalletRestoreAccessChallenge,
@@ -25,12 +19,8 @@ import {
25
19
  type ContinuitySkillsTree,
26
20
  type WalletChallengePurpose,
27
21
  } from '../../continuity/envelope.js'
28
- import {
29
- createAgentCard,
30
- defaultPublicSkillsProfile,
31
- serializeAgentCard,
32
- } from '../../continuity/publicSkills.js'
33
22
  import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
23
+ import { readCustodyMode } from '../custody/state.js'
34
24
  import { addToIpfs, DEFAULT_IPFS_API_URL, isPinataUploadUrl } from '../../storage/ipfs.js'
35
25
  import {
36
26
  createErc8004PublicClient,
@@ -83,19 +73,19 @@ import {
83
73
  } from './completion.js'
84
74
 
85
75
  type BackupMetadata = NonNullable<EthagentIdentity['backup']>
86
- type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
76
+ type AgentCardMetadata = NonNullable<EthagentIdentity['agentCard']>
87
77
 
88
78
  type RebackupPreparedTransaction = {
89
79
  ownerAddress: Address
90
80
  agentUri: string
91
81
  metadataCid: string
92
82
  backup: BackupMetadata
93
- publicSkills: PublicSkillsMetadata
83
+ agentCard: AgentCardMetadata
94
84
  identity: EthagentIdentity
95
85
  markdownScaffold?: IdentityMarkdownScaffold
96
86
  publishedSources: {
97
87
  privateFiles: ContinuityFiles
98
- publicSkills: string
88
+ agentCard: string
99
89
  skills: ContinuitySkillsTree
100
90
  }
101
91
  }
@@ -197,8 +187,9 @@ async function runRebackupSigningInner(
197
187
  return
198
188
  }
199
189
  const snapshotOwner = ownerAddressForSnapshotSave(step.identity, step.profileUpdates)
190
+ const isAdvanced = readCustodyMode(step.identity.state as Record<string, unknown> | undefined) === 'advanced'
200
191
  const purpose: WalletPurpose = step.walletPurpose
201
- ?? (step.vaultAddress ? 'rotate-agent-uri-vault-owner' : rebackupWalletPurpose(step.identity, step.profileUpdates))
192
+ ?? (step.vaultAddress && isAdvanced ? 'rotate-agent-uri-vault-owner' : rebackupWalletPurpose(step.identity, step.profileUpdates))
202
193
  const challengePurpose: WalletChallengePurpose = 'restore-owner'
203
194
  const walletAccess = walletRestoreAccessContext(step.identity, step.registry, step.profileUpdates, snapshotOwner)
204
195
  if (!walletAccess) throw new Error('Cannot back up: missing wallet restore access context')
@@ -244,20 +235,8 @@ async function runRebackupSigningInner(
244
235
  const continuityFiles = markdownScaffold
245
236
  ? { 'SOUL.md': markdownScaffold['SOUL.md'], 'MEMORY.md': markdownScaffold['MEMORY.md'] }
246
237
  : await readContinuityFiles(nextIdentityForFiles)
247
- const publicSkillsJson = await syncPublicSkillsManifest(nextIdentityForFiles)
248
- const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
249
- assertVerifiedPin(publicSkillsPin)
250
- const publicSkillEntries = await derivePublicSkillEntries(nextIdentityForFiles)
251
- const augmentedPublicProfile = appendPublicSkillEntries(
252
- basePublicSkillsProfile(nextIdentityForFiles),
253
- publicSkillEntries,
254
- )
255
- const agentCardPin = await addToIpfs(
256
- DEFAULT_IPFS_API_URL,
257
- serializeAgentCard(createAgentCard(augmentedPublicProfile)),
258
- fetch,
259
- { pinataJwt: step.pinataJwt },
260
- )
238
+ const agentCardJson = await syncAgentCardManifest(nextIdentityForFiles)
239
+ const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, agentCardJson, fetch, { pinataJwt: step.pinataJwt })
261
240
  assertVerifiedPin(agentCardPin)
262
241
  const skillsTree = await prepareSyncedSkillsTree(nextIdentityForFiles)
263
242
  const envelope = createContinuityEnvelopeForSave({
@@ -287,9 +266,8 @@ async function runRebackupSigningInner(
287
266
  identityRegistryAddress: step.registry.identityRegistryAddress,
288
267
  agentId: sourceAgentId,
289
268
  }
290
- const publicSkills: PublicSkillsMetadata = {
291
- cid: publicSkillsPin.cid,
292
- agentCardCid: agentCardPin.cid,
269
+ const agentCard: AgentCardMetadata = {
270
+ cid: agentCardPin.cid,
293
271
  updatedAt: envelope.createdAt,
294
272
  status: 'pinned',
295
273
  }
@@ -300,7 +278,7 @@ async function runRebackupSigningInner(
300
278
  ...(uploadedImageUri ? { image: uploadedImageUri } : {}),
301
279
  }, {
302
280
  backup: { cid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt },
303
- publicDiscovery: { skillsCid: publicSkills.cid, agentCardCid: publicSkills.agentCardCid, updatedAt: publicSkills.updatedAt },
281
+ publicDiscovery: { agentCardCid: agentCard.cid, updatedAt: agentCard.updatedAt },
304
282
  registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress, agentId: sourceAgentId },
305
283
  ensName: nextEnsName,
306
284
  operators: operatorsPointerFromState(state, nextEnsName),
@@ -332,12 +310,12 @@ async function runRebackupSigningInner(
332
310
  agentUri,
333
311
  metadataCid,
334
312
  backup: { ...backup, metadataCid, agentUri },
335
- publicSkills,
313
+ agentCard,
336
314
  identity: { ...step.identity, state },
337
315
  ...(markdownScaffold ? { markdownScaffold } : {}),
338
316
  publishedSources: {
339
317
  privateFiles: continuityFiles,
340
- publicSkills: publicSkillsJson,
318
+ agentCard: agentCardJson,
341
319
  skills: skillsTree,
342
320
  },
343
321
  },
@@ -357,12 +335,12 @@ async function runRebackupSigningInner(
357
335
  agentUri,
358
336
  metadataCid,
359
337
  backup: { ...backup, metadataCid, agentUri },
360
- publicSkills,
338
+ agentCard,
361
339
  identity: { ...step.identity, state },
362
340
  ...(markdownScaffold ? { markdownScaffold } : {}),
363
341
  publishedSources: {
364
342
  privateFiles: continuityFiles,
365
- publicSkills: publicSkillsJson,
343
+ agentCard: agentCardJson,
366
344
  skills: skillsTree,
367
345
  },
368
346
  },
@@ -388,7 +366,7 @@ async function runRebackupSigningInner(
388
366
  agentUri: result.prepared.agentUri,
389
367
  metadataCid: result.prepared.metadataCid,
390
368
  backup: { ...result.prepared.backup, txHash: result.txHash },
391
- publicSkills: result.prepared.publicSkills,
369
+ agentCard: result.prepared.agentCard,
392
370
  }
393
371
  if (result.prepared.markdownScaffold) {
394
372
  await writeIdentityMarkdownScaffold(nextIdentity, result.prepared.markdownScaffold)
@@ -22,7 +22,7 @@ export const NewSkillVisibilityScreen: React.FC<NewSkillVisibilityScreenProps> =
22
22
  }) => (
23
23
  <Surface
24
24
  title={`Visibility · ${name}`}
25
- subtitle="Discoverable is the default. You can change it later from Change Visibility."
25
+ subtitle="Public is the default. You can change it later from Change Visibility."
26
26
  footer={footer}
27
27
  >
28
28
  {error && (
@@ -33,9 +33,8 @@ export const NewSkillVisibilityScreen: React.FC<NewSkillVisibilityScreenProps> =
33
33
  <Box marginTop={1}>
34
34
  <Select<SkillVisibility | 'back'>
35
35
  options={[
36
- { value: 'private', label: 'Private', hint: 'Local-only. Not in skills.json.' },
37
- { value: 'discoverable', label: 'Discoverable', hint: 'Default. Indexed in skills.json with description.' },
38
- { value: 'public', label: 'Public', hint: 'Indexed in skills.json and Agent Card.' },
36
+ { value: 'private', label: 'Private', hint: 'Local-only. Not in the Agent Card.' },
37
+ { value: 'public', label: 'Public', hint: 'Default. Listed in the Agent Card.' },
39
38
  { value: 'back', role: 'section', label: 'Navigation' },
40
39
  { value: 'back', label: 'Back', hint: 'Return to the name step', role: 'utility' },
41
40
  ]}
@@ -74,7 +74,6 @@ export const SkillActionsScreen: React.FC<SkillActionsScreenProps> = ({
74
74
 
75
75
  options.push({ value: noop, role: 'section', label: 'Visibility' })
76
76
  options.push(visibilityOption('private', visibility))
77
- options.push(visibilityOption('discoverable', visibility))
78
77
  options.push(visibilityOption('public', visibility))
79
78
 
80
79
  options.push({ value: noop, role: 'section', label: 'Manage' })
@@ -83,6 +82,7 @@ export const SkillActionsScreen: React.FC<SkillActionsScreenProps> = ({
83
82
  label: 'Delete',
84
83
  hint: 'Remove this skill folder and its supporting files',
85
84
  })
85
+ options.push({ value: noop, role: 'section', label: 'Return' })
86
86
  options.push({
87
87
  value: { kind: 'back' },
88
88
  label: 'Back',
@@ -140,9 +140,8 @@ function visibilityOption(level: SkillVisibility, current?: SkillVisibility): Se
140
140
  }
141
141
 
142
142
  function visibilityHint(level: SkillVisibility): string {
143
- if (level === 'private') return 'Local-only. Not in skills.json.'
144
- if (level === 'discoverable') return 'Default. Indexed with description.'
145
- return 'Indexed with description and Agent Card link.'
143
+ if (level === 'private') return 'Local-only. Not in the Agent Card.'
144
+ return 'Default. Listed in the Agent Card.'
146
145
  }
147
146
 
148
147
  function capitalize(value: string): string {
@@ -73,7 +73,7 @@ export const SkillsTreeScreen: React.FC<SkillsTreeScreenProps> = ({
73
73
  }
74
74
  }, [identity, editorOpened])
75
75
 
76
- const subtitle = notice ?? 'Open a skill, create one, or remove one.'
76
+ const subtitle = notice ?? 'Select a skill to open, change visibility, or delete.'
77
77
  const isLoading = tree === null
78
78
  const skills = tree?.skills ?? []
79
79
  const supportingCounts = tree?.supportingCounts ?? {}
@@ -145,6 +145,7 @@ function buildOptions(
145
145
  const supportCount = supportingCounts[skill.name] ?? 0
146
146
  const meta = [capitalize(skill.visibility)]
147
147
  if (supportCount > 0) meta.push(`${supportCount + 1} files`)
148
+ meta.push('enter for actions')
148
149
  rows.push({
149
150
  value: { kind: 'skill', relativePath: skill.relativePath },
150
151
  label: `${branch}${skill.name}/SKILL.md`,
@@ -27,7 +27,7 @@ export function changedContinuitySnapshotFiles(
27
27
  const result: string[] = []
28
28
  if (changed('SOUL.md')) result.push('SOUL.md')
29
29
  if (changed('MEMORY.md')) result.push('MEMORY.md')
30
- if (changed('skills.json') || changed('private-skills')) result.push('Skills')
30
+ if (changed('agent-card.json') || changed('private-skills')) result.push('Skills')
31
31
  return result
32
32
  }
33
33
 
@@ -4,7 +4,6 @@ import {
4
4
  prepareSyncedIdentityMarkdownScaffold,
5
5
  prepareSyncedSkillsTree,
6
6
  readContinuityFiles,
7
- readPublicSkillsFile,
8
7
  writeIdentityMarkdownScaffold,
9
8
  type IdentityMarkdownScaffold,
10
9
  } from '../../continuity/storage.js'
@@ -16,14 +15,7 @@ import {
16
15
  type WalletChallengePurpose,
17
16
  } from '../../continuity/envelope.js'
18
17
  import {
19
- appendPublicSkillEntries,
20
- createAgentCard,
21
- defaultPublicSkillsProfile,
22
- serializeAgentCard,
23
- } from '../../continuity/publicSkills.js'
24
- import {
25
- derivePublicSkillEntries,
26
- syncPublicSkillsManifest,
18
+ syncAgentCardManifest,
27
19
  } from '../../continuity/skills/publicSkillsSync.js'
28
20
  import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
29
21
  import { addToIpfs, DEFAULT_IPFS_API_URL } from '../../storage/ipfs.js'
@@ -60,7 +52,7 @@ import {
60
52
  import { markCurrentContinuityFilesPublished } from '../shared/effects/sync.js'
61
53
 
62
54
  type BackupMetadata = NonNullable<EthagentIdentity['backup']>
63
- type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
55
+ type AgentCardMetadata = NonNullable<EthagentIdentity['agentCard']>
64
56
 
65
57
  type VaultPublishPrepared = {
66
58
  nextIdentity: EthagentIdentity
@@ -68,7 +60,7 @@ type VaultPublishPrepared = {
68
60
  completionMessage: string
69
61
  publishedSources: {
70
62
  privateFiles: ContinuityFiles
71
- publicSkills: string
63
+ agentCard: string
72
64
  skills: ContinuitySkillsTree
73
65
  }
74
66
  }
@@ -142,20 +134,8 @@ export async function runOperatorWalletRebackup(args: {
142
134
  const continuityFiles = markdownScaffold
143
135
  ? { 'SOUL.md': markdownScaffold['SOUL.md'], 'MEMORY.md': markdownScaffold['MEMORY.md'] }
144
136
  : await readContinuityFiles(nextIdentityForFiles)
145
- const publicSkillsJson = await syncPublicSkillsManifest(nextIdentityForFiles)
146
- const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
147
- assertVerifiedPin(publicSkillsPin)
148
- const publicSkillEntries = await derivePublicSkillEntries(nextIdentityForFiles)
149
- const augmentedPublicProfile = appendPublicSkillEntries(
150
- defaultPublicSkillsProfile(nextIdentityForFiles),
151
- publicSkillEntries,
152
- )
153
- const agentCardPin = await addToIpfs(
154
- DEFAULT_IPFS_API_URL,
155
- serializeAgentCard(createAgentCard(augmentedPublicProfile)),
156
- fetch,
157
- { pinataJwt: step.pinataJwt },
158
- )
137
+ const agentCardJson = await syncAgentCardManifest(nextIdentityForFiles)
138
+ const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, agentCardJson, fetch, { pinataJwt: step.pinataJwt })
159
139
  assertVerifiedPin(agentCardPin)
160
140
  const skillsTree = await prepareSyncedSkillsTree(nextIdentityForFiles)
161
141
  const envelope = createContinuityEnvelopeForSave({
@@ -185,9 +165,8 @@ export async function runOperatorWalletRebackup(args: {
185
165
  identityRegistryAddress: step.registry.identityRegistryAddress,
186
166
  agentId: sourceAgentId,
187
167
  }
188
- const publicSkills: PublicSkillsMetadata = {
189
- cid: publicSkillsPin.cid,
190
- agentCardCid: agentCardPin.cid,
168
+ const agentCard: AgentCardMetadata = {
169
+ cid: agentCardPin.cid,
191
170
  updatedAt: envelope.createdAt,
192
171
  status: 'pinned',
193
172
  }
@@ -196,7 +175,7 @@ export async function runOperatorWalletRebackup(args: {
196
175
  ...step.identity,
197
176
  state,
198
177
  backup,
199
- publicSkills,
178
+ agentCard,
200
179
  }
201
180
 
202
181
  if (markdownScaffold) {
@@ -205,7 +184,7 @@ export async function runOperatorWalletRebackup(args: {
205
184
  await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'local operator-wallet snapshot' }).catch(() => null)
206
185
  await markCurrentContinuityFilesPublished(nextIdentity, {
207
186
  privateFiles: continuityFiles,
208
- publicSkills: publicSkillsJson,
187
+ agentCard: agentCardJson,
209
188
  skills: skillsTree,
210
189
  }).catch(() => null)
211
190
  const completionMessage = nextEnsName !== undefined && nextEnsName !== ((step.identity.state as Record<string, unknown> | undefined)?.ensName as string | undefined)
@@ -284,20 +263,8 @@ async function runOperatorWalletVaultPublish(args: {
284
263
  const continuityFiles = markdownScaffold
285
264
  ? { 'SOUL.md': markdownScaffold['SOUL.md'], 'MEMORY.md': markdownScaffold['MEMORY.md'] }
286
265
  : await readContinuityFiles(nextIdentityForFiles)
287
- const publicSkillsJson = await syncPublicSkillsManifest(nextIdentityForFiles)
288
- const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
289
- assertVerifiedPin(publicSkillsPin)
290
- const publicSkillEntries = await derivePublicSkillEntries(nextIdentityForFiles)
291
- const augmentedPublicProfile = appendPublicSkillEntries(
292
- defaultPublicSkillsProfile(nextIdentityForFiles),
293
- publicSkillEntries,
294
- )
295
- const agentCardPin = await addToIpfs(
296
- DEFAULT_IPFS_API_URL,
297
- serializeAgentCard(createAgentCard(augmentedPublicProfile)),
298
- fetch,
299
- { pinataJwt: step.pinataJwt },
300
- )
266
+ const agentCardJson = await syncAgentCardManifest(nextIdentityForFiles)
267
+ const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, agentCardJson, fetch, { pinataJwt: step.pinataJwt })
301
268
  assertVerifiedPin(agentCardPin)
302
269
  const skillsTree = await prepareSyncedSkillsTree(nextIdentityForFiles)
303
270
  const envelope = createContinuityEnvelopeForSave({
@@ -315,9 +282,8 @@ async function runOperatorWalletVaultPublish(args: {
315
282
  const statePin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeContinuitySnapshotEnvelope(envelope), fetch, { pinataJwt: step.pinataJwt })
316
283
  assertVerifiedPin(statePin)
317
284
 
318
- const publicSkills: PublicSkillsMetadata = {
319
- cid: publicSkillsPin.cid,
320
- agentCardCid: agentCardPin.cid,
285
+ const agentCard: AgentCardMetadata = {
286
+ cid: agentCardPin.cid,
321
287
  updatedAt: envelope.createdAt,
322
288
  status: 'pinned',
323
289
  }
@@ -329,7 +295,7 @@ async function runOperatorWalletVaultPublish(args: {
329
295
  ...(uploadedImageUri ? { image: uploadedImageUri } : {}),
330
296
  }, {
331
297
  backup: { cid: statePin.cid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt },
332
- publicDiscovery: { skillsCid: publicSkills.cid, agentCardCid: publicSkills.agentCardCid, updatedAt: publicSkills.updatedAt },
298
+ publicDiscovery: { agentCardCid: agentCard.cid, updatedAt: agentCard.updatedAt },
333
299
  registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress, agentId: sourceAgentId },
334
300
  ensName: nextEnsName,
335
301
  operators: operatorsPointerFromState(state, nextEnsName),
@@ -365,7 +331,7 @@ async function runOperatorWalletVaultPublish(args: {
365
331
  ...step.identity,
366
332
  state,
367
333
  backup,
368
- publicSkills,
334
+ agentCard,
369
335
  agentUri,
370
336
  metadataCid,
371
337
  }
@@ -383,7 +349,7 @@ async function runOperatorWalletVaultPublish(args: {
383
349
  completionMessage,
384
350
  publishedSources: {
385
351
  privateFiles: continuityFiles,
386
- publicSkills: publicSkillsJson,
352
+ agentCard: agentCardJson,
387
353
  skills: skillsTree,
388
354
  },
389
355
  },
@@ -14,7 +14,6 @@ import {
14
14
  import {
15
15
  createAgentCard,
16
16
  defaultPublicSkillsProfile,
17
- renderPublicSkillsJson,
18
17
  serializeAgentCard,
19
18
  } from '../../continuity/publicSkills.js'
20
19
  import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
@@ -43,17 +42,17 @@ import { awaitConfirmedReceipt } from '../shared/effects/receipts.js'
43
42
  import { assertVerifiedPin } from '../shared/effects/profilePrep.js'
44
43
 
45
44
  type BackupMetadata = NonNullable<EthagentIdentity['backup']>
46
- type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
45
+ type AgentCardMetadata = NonNullable<EthagentIdentity['agentCard']>
47
46
 
48
47
  type CreatePreparedTransaction = {
49
48
  ownerAddress: Address
50
49
  agentUri: string
51
50
  metadataCid: string
52
51
  backup: BackupMetadata
53
- publicSkills: PublicSkillsMetadata
52
+ agentCard: AgentCardMetadata
54
53
  state: Record<string, unknown>
55
54
  continuityFiles: ReturnType<typeof defaultContinuityFiles>
56
- publicSkillsJson: string
55
+ agentCardJson: string
57
56
  }
58
57
 
59
58
  export async function runCreatePreflight(
@@ -139,10 +138,8 @@ export async function runCreateSigning(
139
138
  })
140
139
  const continuityFiles = defaultContinuityFiles(draftIdentity)
141
140
  const publicProfile = defaultPublicSkillsProfile(draftIdentity)
142
- const publicSkillsJson = renderPublicSkillsJson(publicProfile)
143
- const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
144
- assertVerifiedPin(publicSkillsPin)
145
- const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeAgentCard(createAgentCard(publicProfile)), fetch, { pinataJwt: step.pinataJwt })
141
+ const agentCardJson = serializeAgentCard(createAgentCard(publicProfile))
142
+ const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, agentCardJson, fetch, { pinataJwt: step.pinataJwt })
146
143
  assertVerifiedPin(agentCardPin)
147
144
  const envelope = createContinuitySnapshotEnvelope({
148
145
  ownerAddress: wallet.account,
@@ -168,9 +165,8 @@ export async function runCreateSigning(
168
165
  rpcUrl: step.registry.rpcUrl,
169
166
  identityRegistryAddress: step.registry.identityRegistryAddress,
170
167
  }
171
- const publicSkills: PublicSkillsMetadata = {
172
- cid: publicSkillsPin.cid,
173
- agentCardCid: agentCardPin.cid,
168
+ const agentCard: AgentCardMetadata = {
169
+ cid: agentCardPin.cid,
174
170
  updatedAt: envelope.createdAt,
175
171
  status: 'pinned',
176
172
  }
@@ -181,7 +177,7 @@ export async function runCreateSigning(
181
177
  ...(typeof state.imageUrl === 'string' ? { image: state.imageUrl } : {}),
182
178
  }, {
183
179
  backup: { cid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt },
184
- publicDiscovery: { skillsCid: publicSkills.cid, agentCardCid: publicSkills.agentCardCid, updatedAt: publicSkills.updatedAt },
180
+ publicDiscovery: { agentCardCid: agentCard.cid, updatedAt: agentCard.updatedAt },
185
181
  registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress },
186
182
  ownerAddress: wallet.account,
187
183
  })
@@ -197,10 +193,10 @@ export async function runCreateSigning(
197
193
  agentUri,
198
194
  metadataCid,
199
195
  backup: { ...backup, metadataCid, agentUri },
200
- publicSkills,
196
+ agentCard,
201
197
  state,
202
198
  continuityFiles,
203
- publicSkillsJson,
199
+ agentCardJson,
204
200
  },
205
201
  }
206
202
  },
@@ -233,11 +229,11 @@ export async function runCreateSigning(
233
229
  metadataCid: result.prepared.metadataCid,
234
230
  state: result.prepared.state,
235
231
  backup,
236
- publicSkills: result.prepared.publicSkills,
232
+ agentCard: result.prepared.agentCard,
237
233
  }
238
234
  await writeIdentityMarkdownScaffold(nextIdentity, {
239
235
  ...defaultContinuityFiles(nextIdentity),
240
- 'skills.json': result.prepared.publicSkillsJson,
236
+ 'agent-card.json': result.prepared.agentCardJson,
241
237
  })
242
238
  await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'initial published snapshot' }).catch(() => null)
243
239
  await callbacks.onIdentityComplete(nextIdentity, `ERC-8004 agent registered · #${registered.agentId.toString()}`, 'create')
@@ -2,8 +2,8 @@ import React from 'react'
2
2
  import { getAddress, type Address } from 'viem'
3
3
  import type { BrowserWalletReady } from '../../wallet/browserWallet.js'
4
4
  import {
5
- AGENT_RECORD_READ_KEY_LIST,
6
5
  buildAgentEnsRecords,
6
+ buildEnsip25Key,
7
7
  diffRecords,
8
8
  recordsFromTextMap,
9
9
  } from '../../ens/agentRecords.js'
@@ -137,8 +137,15 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
137
137
  setPhase({ kind: 'validating', fullName, mode, ownerAddress: phaseOwnerAddress, operatorWallet })
138
138
  try {
139
139
  const validation = await validateAgentEnsLink(fullName, ownerAddress)
140
- const currentText = validation.ok
141
- ? await readEthagentTextRecords(fullName, AGENT_RECORD_READ_KEY_LIST)
140
+ const readKeys = identity.agentId
141
+ ? [buildEnsip25Key({
142
+ chainId: registry.chainId,
143
+ identityRegistryAddress: registry.identityRegistryAddress,
144
+ agentId: identity.agentId,
145
+ })]
146
+ : []
147
+ const currentText = validation.ok && readKeys.length > 0
148
+ ? await readEthagentTextRecords(fullName, readKeys)
142
149
  : {}
143
150
  const current = recordsFromTextMap(currentText)
144
151
  const next = buildAgentEnsRecords({
@@ -252,7 +259,14 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
252
259
  const runUnlinkEnsLoading = React.useCallback((fullName: string): void => {
253
260
  setValidationError(null)
254
261
  setPhase({ kind: 'unlink-loading', fullName })
255
- readEthagentTextRecords(fullName, AGENT_RECORD_READ_KEY_LIST)
262
+ const readKeys = identity.agentId
263
+ ? [buildEnsip25Key({
264
+ chainId: registry.chainId,
265
+ identityRegistryAddress: registry.identityRegistryAddress,
266
+ agentId: identity.agentId,
267
+ })]
268
+ : []
269
+ readEthagentTextRecords(fullName, readKeys)
256
270
  .then(currentText => {
257
271
  const currentRecords = recordsFromTextMap(currentText)
258
272
  setPhase({
@@ -266,7 +280,7 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
266
280
  setValidationError(err instanceof Error ? err.message : String(err))
267
281
  setPhase({ kind: 'mode-select' })
268
282
  })
269
- }, [])
283
+ }, [identity.agentId, registry.chainId, registry.identityRegistryAddress])
270
284
 
271
285
  const maintenanceScreen = renderEnsMaintenancePhase({
272
286
  phase,
@@ -4,11 +4,9 @@ import { getAddress, type Address } from 'viem'
4
4
  import { Surface } from '../../../ui/Surface.js'
5
5
  import { Select, type SelectOption } from '../../../ui/Select.js'
6
6
  import { theme } from '../../../ui/theme.js'
7
- import {
8
- formatRecordValue,
9
- recordLabel,
10
- type AgentEnsRecords,
11
- type AgentRecordDiff,
7
+ import type {
8
+ AgentEnsRecords,
9
+ AgentRecordDiff,
12
10
  } from '../../ens/agentRecords.js'
13
11
  import type { EnsValidation } from '../../ens/ensLookup.js'
14
12
  import type {
@@ -194,7 +192,9 @@ export const EnsSetupBlockedScreen: React.FC<EnsSetupBlockedScreenProps> = ({
194
192
  {isSimple
195
193
  ? <EnsSetupRow label="Wallet" value={fallback.ownerAddress ? shortAddress(fallback.ownerAddress) : shortAddress(fallback.operatorAddress)} />
196
194
  : (fallback.ownerAddress ? <EnsSetupRow label="Owner wallet" value={shortAddress(fallback.ownerAddress)} /> : null)}
197
- {fallback.nextRecords?.token ? <EnsSetupRow label="Token link" value={fallback.nextRecords.token} /> : null}
195
+ {fallback.nextRecords && Object.keys(fallback.nextRecords).length > 0
196
+ ? <EnsSetupRow label="Attestation" value="ENSIP-25 agent-registration record" />
197
+ : null}
198
198
  <EnsSetupRow label="Address" value={`Set the subdomain address record to the ${isSimple ? 'connected wallet' : 'owner wallet'}.`} />
199
199
  {!isSimple
200
200
  ? (
@@ -271,7 +271,7 @@ export const UnlinkEnsReviewScreen: React.FC<UnlinkEnsReviewScreenProps> = ({
271
271
  {changedDiffs.map(diff => (
272
272
  <Text key={diff.key}>
273
273
  <Text color={theme.dim}>{` ${diff.key} `}</Text>
274
- <Text color={theme.accentPeriwinkle}>{formatRecordValue(diff.field, diff.current)}</Text>
274
+ <Text color={theme.accentPeriwinkle}>{diff.current}</Text>
275
275
  </Text>
276
276
  ))}
277
277
  </Box>
@@ -420,16 +420,16 @@ export const ReviewScreen: React.FC<ReviewScreenProps> = ({
420
420
  : null}
421
421
  {recordsDiff.map(diff => (
422
422
  <Text key={diff.key}>
423
- <Text color={theme.dim}>{`- ${recordLabel(diff.field)}: `}</Text>
423
+ <Text color={theme.dim}>{`- ${diff.key}: `}</Text>
424
424
  {diff.changed
425
425
  ? (
426
426
  <>
427
- {renderRecordValue(diff.field, diff.current)}
427
+ {renderRecordValue(diff.current)}
428
428
  <Text color={theme.dim}>{' → '}</Text>
429
- {renderRecordValue(diff.field, diff.next)}
429
+ {renderRecordValue(diff.next)}
430
430
  </>
431
431
  )
432
- : renderRecordValue(diff.field, diff.next)}
432
+ : renderRecordValue(diff.next)}
433
433
  </Text>
434
434
  ))}
435
435
  {!hasRecordChanges
@@ -4,10 +4,6 @@ import type { Address } from 'viem'
4
4
  import { Surface } from '../../../ui/Surface.js'
5
5
  import { TextInput } from '../../../ui/TextInput.js'
6
6
  import { theme } from '../../../ui/theme.js'
7
- import {
8
- formatRecordValue,
9
- type AgentEnsRecordState,
10
- } from '../../ens/agentRecords.js'
11
7
  import {
12
8
  isEthDomain,
13
9
  sanitizeSubdomainPrefix,
@@ -24,9 +20,9 @@ import type { EnsEditProps } from './types.js'
24
20
 
25
21
  export const footerHint = (hint: string) => <Text color={theme.dim}>{hint}</Text>
26
22
 
27
- export const renderRecordValue = (field: keyof AgentEnsRecordState, value: string) =>
23
+ export const renderRecordValue = (value: string) =>
28
24
  value
29
- ? <Text color={theme.accentPeriwinkle}>{formatRecordValue(field, value)}</Text>
25
+ ? <Text color={theme.accentPeriwinkle}>{value}</Text>
30
26
  : <Text color={theme.dim}>Unset</Text>
31
27
 
32
28
  export function rootErrorMessage(
@@ -18,7 +18,7 @@ export function recordsHaveCurrentValues(recordsDiff: AgentRecordDiff[]): boolea
18
18
  }
19
19
 
20
20
  export function emptyAgentEnsRecords(): AgentEnsRecords {
21
- return { token: '' }
21
+ return {}
22
22
  }
23
23
 
24
24
  export function unlinkEnsLinkOptions(savedCustodyMode: CustodyMode | undefined, savedOwnerAddress: string): EnsLinkOptions {
@@ -84,8 +84,6 @@ export function manualReasonTitle(reason: EnsSetupBlockedPlan['reason']): string
84
84
  case 'wrapped-parent':
85
85
  case 'subdomain-wrapped':
86
86
  return 'ENS NameWrapper ownership could not be verified'
87
- case 'token-record-collision':
88
- return 'Subdomain already points to another token'
89
87
  case 'token-owner-mismatch':
90
88
  return 'Owner wallet does not own this ERC-8004 token'
91
89
  case 'token-owner-lookup-failed':