ethagent 3.2.0 → 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 (54) 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 -1
  22. package/src/identity/hub/continuity/effects.ts +17 -39
  23. package/src/identity/hub/continuity/skills/NewSkillVisibilityScreen.tsx +2 -2
  24. package/src/identity/hub/continuity/skills/SkillActionsScreen.tsx +2 -2
  25. package/src/identity/hub/continuity/state.ts +1 -1
  26. package/src/identity/hub/continuity/vault.ts +16 -50
  27. package/src/identity/hub/create/effects.ts +12 -16
  28. package/src/identity/hub/ens/EnsEditFlow.tsx +19 -5
  29. package/src/identity/hub/ens/EnsEditReviewScreens.tsx +11 -11
  30. package/src/identity/hub/ens/EnsEditShared.tsx +2 -6
  31. package/src/identity/hub/ens/editCopy.ts +1 -3
  32. package/src/identity/hub/ens/transactions.ts +67 -18
  33. package/src/identity/hub/profile/effects.ts +15 -30
  34. package/src/identity/hub/profile/identity.ts +2 -4
  35. package/src/identity/hub/profile/operatorSave.ts +10 -30
  36. package/src/identity/hub/restore/RestoreFlow.tsx +9 -9
  37. package/src/identity/hub/restore/apply.ts +7 -8
  38. package/src/identity/hub/restore/helpers.ts +3 -3
  39. package/src/identity/hub/restore/recovery.ts +9 -10
  40. package/src/identity/hub/restore/resolve.ts +11 -9
  41. package/src/identity/hub/shared/components/MenuScreen.tsx +3 -3
  42. package/src/identity/hub/shared/components/UnlinkedIdentityScreen.tsx +2 -2
  43. package/src/identity/hub/shared/effects/sync.ts +1 -1
  44. package/src/identity/hub/transfer/TokenTransferScreens.tsx +1 -1
  45. package/src/identity/hub/transfer/effects.ts +10 -31
  46. package/src/identity/hub/useIdentityHubContinuity.ts +12 -12
  47. package/src/identity/hub/useIdentityHubController.ts +12 -3
  48. package/src/identity/registry/erc8004/metadata.ts +10 -27
  49. package/src/identity/registry/erc8004/types.ts +0 -1
  50. package/src/storage/config.ts +1 -2
  51. package/src/storage/identity.ts +3 -3
  52. package/src/storage/rewind.ts +1 -1
  53. package/src/tools/privateContinuityEditTool.ts +4 -4
  54. package/src/utils/withRetry.ts +2 -2
@@ -1,51 +1,79 @@
1
- export const AGENT_RECORD_KEYS = {
2
- token: 'org.ethagent.token',
3
- } as const
1
+ import type { Address } from 'viem'
2
+ import { encodeInteroperableAddress } from './erc7930.js'
4
3
 
5
- type AgentRecordKey = typeof AGENT_RECORD_KEYS[keyof typeof AGENT_RECORD_KEYS]
4
+ export const AGENT_TOKEN_RECORD_KEY = 'org.ethagent.token'
6
5
 
7
- export const AGENT_RECORD_KEY_LIST: readonly AgentRecordKey[] = [
8
- AGENT_RECORD_KEYS.token,
9
- ] as const
10
-
11
- export const AGENT_RECORD_READ_KEY_LIST: readonly string[] = AGENT_RECORD_KEY_LIST
12
-
13
- export type AgentEnsRecords = {
14
- token?: string
15
- }
16
-
17
- export type AgentEnsRecordState = AgentEnsRecords
6
+ export type AgentEnsRecords = Record<string, string>
7
+ export type AgentEnsRecordState = Record<string, string>
18
8
 
19
9
  export type AgentRecordDiff = {
20
- key: AgentRecordKey
21
- field: keyof AgentEnsRecordState
10
+ key: string
22
11
  current: string
23
12
  next: string
24
13
  changed: boolean
25
14
  }
26
15
 
27
- const FIELD_FOR_KEY: Record<AgentRecordKey, keyof AgentEnsRecords> = {
28
- [AGENT_RECORD_KEYS.token]: 'token',
16
+ export type Ensip25KeyArgs = {
17
+ chainId: number
18
+ identityRegistryAddress: Address | string
19
+ agentId: string | bigint
20
+ }
21
+
22
+ export function buildEnsip25Key(args: Ensip25KeyArgs): string {
23
+ const agentIdStr = typeof args.agentId === 'bigint' ? args.agentId.toString() : args.agentId.trim()
24
+ if (!agentIdStr) throw new Error('agentId is required to build the ENSIP-25 record key')
25
+ if (agentIdStr.includes('[') || agentIdStr.includes(']')) {
26
+ throw new Error('agentId must not contain square brackets per ENSIP-25')
27
+ }
28
+ const registry = encodeInteroperableAddress({
29
+ chainId: args.chainId,
30
+ address: args.identityRegistryAddress as Address,
31
+ })
32
+ return `agent-registration[${registry}][${agentIdStr}]`
29
33
  }
30
34
 
31
- const LABEL_FOR_FIELD: Record<keyof AgentEnsRecordState, string> = {
32
- token: 'Agent token',
35
+ export function buildAgentTokenReferenceValue(args: Ensip25KeyArgs): string {
36
+ const agentIdStr = typeof args.agentId === 'bigint' ? args.agentId.toString() : args.agentId.trim()
37
+ return `eip155:${args.chainId}:${(args.identityRegistryAddress as string).toLowerCase()}:${agentIdStr}`
33
38
  }
34
39
 
35
- export function recordsFromTextMap(text: Record<string, string>): AgentEnsRecordState {
40
+ export function buildAgentEnsRecords(args: {
41
+ chainId: number
42
+ identityRegistryAddress: Address | string
43
+ agentId: string | bigint | undefined
44
+ }): AgentEnsRecords {
45
+ if (args.agentId === undefined || args.agentId === '') return {}
46
+ const ensip25Key = buildEnsip25Key({
47
+ chainId: args.chainId,
48
+ identityRegistryAddress: args.identityRegistryAddress,
49
+ agentId: args.agentId,
50
+ })
51
+ const tokenValue = buildAgentTokenReferenceValue({
52
+ chainId: args.chainId,
53
+ identityRegistryAddress: args.identityRegistryAddress,
54
+ agentId: args.agentId,
55
+ })
36
56
  return {
37
- token: text[AGENT_RECORD_KEYS.token] ?? '',
57
+ [ensip25Key]: '1',
58
+ [AGENT_TOKEN_RECORD_KEY]: tokenValue,
38
59
  }
39
60
  }
40
61
 
62
+ export function recordsFromTextMap(text: Record<string, string>): AgentEnsRecordState {
63
+ const out: AgentEnsRecordState = {}
64
+ for (const [key, value] of Object.entries(text)) {
65
+ if (value) out[key] = value
66
+ }
67
+ return out
68
+ }
69
+
41
70
  export function diffRecords(current: AgentEnsRecordState, next: AgentEnsRecords): AgentRecordDiff[] {
42
- return AGENT_RECORD_KEY_LIST.map(key => {
43
- const field = FIELD_FOR_KEY[key]
44
- const currentValue = (current[field] ?? '').trim()
45
- const nextValue = (next[field] ?? '').trim()
71
+ const keys = new Set<string>([...Object.keys(current), ...Object.keys(next)])
72
+ return Array.from(keys).map(key => {
73
+ const currentValue = (current[key] ?? '').trim()
74
+ const nextValue = (next[key] ?? '').trim()
46
75
  return {
47
76
  key,
48
- field,
49
77
  current: currentValue,
50
78
  next: nextValue,
51
79
  changed: currentValue !== nextValue,
@@ -61,22 +89,10 @@ export function changedRecords(current: AgentEnsRecordState, next: AgentEnsRecor
61
89
  return out
62
90
  }
63
91
 
64
- export function recordLabel(field: keyof AgentEnsRecordState): string {
65
- return LABEL_FOR_FIELD[field]
66
- }
67
-
68
- export function formatRecordValue(_field: keyof AgentEnsRecordState, value: string): string {
69
- return value
70
- }
71
-
72
- export function buildAgentEnsRecords(args: {
73
- chainId: number
74
- identityRegistryAddress: string
75
- agentId: string | undefined
76
- }): AgentEnsRecords {
77
- const records: AgentEnsRecords = {}
78
- if (args.agentId) {
79
- records.token = `eip155:${args.chainId}:${args.identityRegistryAddress.toLowerCase()}:${args.agentId}`
92
+ export function clearedRecords(current: AgentEnsRecordState): AgentEnsRecords {
93
+ const out: AgentEnsRecords = {}
94
+ for (const key of Object.keys(current)) {
95
+ out[key] = ''
80
96
  }
81
- return records
97
+ return out
82
98
  }
@@ -9,7 +9,6 @@ import {
9
9
  } from 'viem'
10
10
  import { mainnet } from 'viem/chains'
11
11
  import {
12
- AGENT_RECORD_READ_KEY_LIST,
13
12
  recordsFromTextMap,
14
13
  type AgentEnsRecordState,
15
14
  } from '../agentRecords.js'
@@ -81,9 +80,14 @@ export async function readAddressRecord(client: EnsAutomationReadClient, resolve
81
80
  }
82
81
  }
83
82
 
84
- export async function readTextRecords(client: EnsAutomationReadClient, resolverAddress: Address, node: Hex): Promise<AgentEnsRecordState> {
83
+ export async function readTextRecords(
84
+ client: EnsAutomationReadClient,
85
+ resolverAddress: Address,
86
+ node: Hex,
87
+ keys: readonly string[],
88
+ ): Promise<AgentEnsRecordState> {
85
89
  const text: Record<string, string> = {}
86
- for (const key of AGENT_RECORD_READ_KEY_LIST) {
90
+ for (const key of keys) {
87
91
  try {
88
92
  const value = await client.readContract({
89
93
  address: resolverAddress,
@@ -105,10 +109,3 @@ export function isZero(address: Address): boolean {
105
109
  export function sameAddress(a: Address, b: Address): boolean {
106
110
  return a.toLowerCase() === b.toLowerCase()
107
111
  }
108
-
109
- export function normalizeAgentRecords(records: AgentEnsRecordState): AgentEnsRecordState {
110
- return {
111
- ...records,
112
- ...(records.token ? { token: records.token.toLowerCase() } : {}),
113
- }
114
- }
@@ -1,6 +1,8 @@
1
1
  import { getAddress, namehash, type Address } from 'viem'
2
2
  import {
3
+ AGENT_TOKEN_RECORD_KEY,
3
4
  buildAgentEnsRecords,
5
+ buildEnsip25Key,
4
6
  diffRecords,
5
7
  } from '../agentRecords.js'
6
8
  import { normalizeEthDomain, splitSubdomainName } from '../ensLookup.js'
@@ -12,7 +14,6 @@ import {
12
14
  import {
13
15
  createEnsAutomationClient,
14
16
  isZero,
15
- normalizeAgentRecords,
16
17
  readAddressRecord,
17
18
  readOwner,
18
19
  readResolver,
@@ -225,26 +226,19 @@ export async function preflightEnsSetup(args: EnsSetupPreflightArgs): Promise<En
225
226
  }
226
227
 
227
228
  const currentAddress = isZero(childResolver) ? null : await readAddressRecord(client, resolverAddress, fullNode)
228
- const currentRecords = normalizeAgentRecords(isZero(childResolver) ? {} : await readTextRecords(client, resolverAddress, fullNode))
229
+ const ensip25Key = buildEnsip25Key({
230
+ chainId: args.registry.chainId,
231
+ identityRegistryAddress: args.registry.identityRegistryAddress,
232
+ agentId: String(args.agentId),
233
+ })
234
+ const currentRecords = isZero(childResolver)
235
+ ? {}
236
+ : await readTextRecords(client, resolverAddress, fullNode, [ensip25Key, AGENT_TOKEN_RECORD_KEY])
229
237
  const nextRecords = buildAgentEnsRecords({
230
238
  chainId: args.registry.chainId,
231
239
  identityRegistryAddress: args.registry.identityRegistryAddress,
232
240
  agentId: String(args.agentId),
233
241
  })
234
- if (currentRecords.token && nextRecords.token && currentRecords.token !== nextRecords.token) {
235
- return manual(args, {
236
- rootName,
237
- label,
238
- fullName,
239
- operatorAddress,
240
- ownerAddress,
241
- resolverAddress,
242
- reason: 'token-record-collision',
243
- detail: `${fullName} already points to another ERC-8004 token`,
244
- currentRecords,
245
- nextRecords,
246
- })
247
- }
248
242
 
249
243
  const recordDiffs = diffRecords(currentRecords, nextRecords)
250
244
  const addressChanged = !currentAddress || !sameAddress(currentAddress, ownerAddress)
@@ -56,7 +56,6 @@ export type EnsSetupBlockedPlan = {
56
56
  | 'operator-matches-owner'
57
57
  | 'token-owner-mismatch'
58
58
  | 'token-owner-lookup-failed'
59
- | 'token-record-collision'
60
59
  | 'lookup-failed'
61
60
  detail: string
62
61
  currentRecords?: AgentEnsRecordState
@@ -24,6 +24,7 @@ export {
24
24
  encodeEnsRecordsTransaction,
25
25
  encodeEnsRegistryTransaction,
26
26
  } from './ensAutomation/transactions.js'
27
+ export { readAddressRecord } from './ensAutomation/read.js'
27
28
  export { isRootEthName } from './ensAutomation/names.js'
28
29
  export { preflightDeleteSubdomain } from './ensAutomation/delete.js'
29
30
  export { compareOperatorSets } from './ensAutomation/operators.js'
@@ -11,7 +11,7 @@ export type EncodedEnsRecordTransaction = {
11
11
  calls: Hex[]
12
12
  }
13
13
 
14
- export async function encodeSetEthagentTextRecords(
14
+ export async function encodeSetEnsip25TextRecord(
15
15
  fullName: string,
16
16
  records: Record<string, string>,
17
17
  opts: DiscoverOptions = {},
@@ -16,4 +16,4 @@ export { resolveEnsAddress, readEthagentTextRecords, readResolverAddress } from
16
16
  export { parseAgentTokenReference } from './ensLookup/tokenReference.js'
17
17
  export { discoverOwnedEnsNameDetails, discoverOwnedEnsNames } from './ensLookup/discovery.js'
18
18
  export { validateAgentEnsLink } from './ensLookup/validation.js'
19
- export { encodeSetEthagentTextRecords } from './ensLookup/records.js'
19
+ export { encodeSetEnsip25TextRecord } from './ensLookup/records.js'
@@ -0,0 +1,48 @@
1
+ import type { Address } from 'viem'
2
+
3
+ const VERSION = 1
4
+ const CHAIN_TYPE_EIP155 = 0
5
+ const ADDRESS_BYTE_LENGTH = 20
6
+
7
+ export type InteroperableAddressArgs = {
8
+ chainId: number
9
+ address: Address
10
+ }
11
+
12
+ export function encodeInteroperableAddress(args: InteroperableAddressArgs): `0x${string}` {
13
+ if (!Number.isInteger(args.chainId) || args.chainId <= 0) {
14
+ throw new Error(`invalid chainId for ERC-7930: ${args.chainId}`)
15
+ }
16
+ const addressHex = args.address.toLowerCase().replace(/^0x/, '')
17
+ if (!/^[0-9a-f]{40}$/.test(addressHex)) {
18
+ throw new Error(`invalid address for ERC-7930: ${args.address}`)
19
+ }
20
+ const chainRefBytes = minimalBigEndianBytes(BigInt(args.chainId))
21
+ if (chainRefBytes.length > 0xff) {
22
+ throw new Error(`chainId too large for ERC-7930 single-byte length prefix: ${args.chainId}`)
23
+ }
24
+ return `0x${uint16Hex(VERSION)}${uint16Hex(CHAIN_TYPE_EIP155)}${uint8Hex(chainRefBytes.length)}${bytesHex(chainRefBytes)}${uint8Hex(ADDRESS_BYTE_LENGTH)}${addressHex}` as `0x${string}`
25
+ }
26
+
27
+ function uint16Hex(n: number): string {
28
+ return n.toString(16).padStart(4, '0')
29
+ }
30
+
31
+ function uint8Hex(n: number): string {
32
+ return n.toString(16).padStart(2, '0')
33
+ }
34
+
35
+ function minimalBigEndianBytes(value: bigint): Uint8Array {
36
+ if (value === 0n) return new Uint8Array([0])
37
+ const bytes: number[] = []
38
+ let remaining = value
39
+ while (remaining > 0n) {
40
+ bytes.unshift(Number(remaining & 0xffn))
41
+ remaining >>= 8n
42
+ }
43
+ return new Uint8Array(bytes)
44
+ }
45
+
46
+ function bytesHex(bytes: Uint8Array): string {
47
+ return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')
48
+ }
@@ -9,6 +9,7 @@ import {
9
9
  runPublicProfileStorageSubmit,
10
10
  } from './profile/effects.js'
11
11
  import { resolveVaultAddress } from './custody/transactions.js'
12
+ import { readCustodyMode } from './custody/state.js'
12
13
  import { WalletApprovalScreen } from './shared/components/WalletApprovalScreen.js'
13
14
  import { RebackupStorageScreen } from './continuity/RebackupStorageScreen.js'
14
15
  import { BusyScreen } from './shared/components/BusyScreen.js'
@@ -321,12 +322,14 @@ export const IdentityHubOperationalRoutes: React.FC<IdentityHubOperationalRoutes
321
322
 
322
323
  if (step.kind === 'rebackup-signing') {
323
324
  const approval = rebackupWalletApprovalView(step.identity, step.profileUpdates)
325
+ const vaultRouted = Boolean(step.vaultAddress)
326
+ && readCustodyMode(step.identity.state as Record<string, unknown> | undefined) === 'advanced'
324
327
  return (
325
328
  <WalletApprovalScreen
326
329
  title={approval.title}
327
330
  subtitle={custodyFlow.renderRebackupSubtitle(
328
331
  approval.subtitle,
329
- Boolean(step.vaultAddress),
332
+ vaultRouted,
330
333
  )}
331
334
  walletSession={walletSession}
332
335
  label={approval.label}
@@ -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)
@@ -33,8 +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: 'public', label: 'Public', hint: 'Default. 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.' },
38
38
  { value: 'back', role: 'section', label: 'Navigation' },
39
39
  { value: 'back', label: 'Back', hint: 'Return to the name step', role: 'utility' },
40
40
  ]}
@@ -140,8 +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
- return 'Default. 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.'
145
145
  }
146
146
 
147
147
  function capitalize(value: string): string {
@@ -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
  },