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
package/README.md CHANGED
@@ -5,8 +5,8 @@ A privacy-first AI agent with a portable Ethereum identity.
5
5
  Switch providers or machines and the AI agent you customized stays behind. `ethagent` ties the agent to a wallet you own, so its soul, memory, and skills follow you across providers, machines, and models.
6
6
 
7
7
  - **Portable.** The ERC-8004 token is the agent's durable identity. Use the ENS name as a readable handle, or the token ID plus chain as the permanent reference, to restore the same agent anywhere.
8
- - **Private.** Soul and memory are encrypted before they are pinned to IPFS. The wallet signature used to unlock them stays local and never submits a transaction, spends funds, or grants token approval.
9
- - **Public.** The agent URI points to plain JSON for the Agent Card and public skills, so other agents can discover capabilities through ERC-8004 and IPFS.
8
+ - **Private.** Soul, memory, and skills are encrypted before they are pinned to IPFS. The wallet signature used to unlock them stays local and never submits a transaction, spends funds, or grants token approval.
9
+ - **Public.** The agent URI points to a public metadata payload on IPFS that includes the Agent Card, so other agents can discover the agent's capabilities through ERC-8004.
10
10
 
11
11
  <details>
12
12
  <summary><strong>Glossary</strong> (click to expand)</summary>
@@ -16,9 +16,9 @@ Switch providers or machines and the AI agent you customized stays behind. `etha
16
16
  | Owner Wallet | Holds and controls the ERC-8004 agent token. Signs custody changes and, in Simple custody, every URI rotation. |
17
17
  | Operator Wallet | Additional wallet authorized to rotate the onchain URI on behalf of the owner. Used in Advanced custody. Never receives token approval. |
18
18
  | Vault | Immutable per-agent custody contract used in Advanced custody. Holds at most one ERC-8004 token. |
19
- | Snapshot | Encrypted bundle of SOUL.md, MEMORY.md, and session state. Pinned to IPFS; decrypts only against the owner wallet's signature. |
20
- | Agent URI | IPFS URI stored in the ERC-8004 `tokenURI`. Resolves to the agent's published metadata. |
21
- | Agent Card | Public JSON describing the agent: name, description, capabilities, and skills. Other agents fetch it for discovery. |
19
+ | Snapshot | Encrypted bundle of SOUL.md, MEMORY.md, the skills/ tree, and session state. Pinned to IPFS; decrypts only against the owner wallet's signature. |
20
+ | Agent URI | IPFS URI stored in the ERC-8004 `tokenURI`. Resolves to a public metadata payload that references the Agent Card. |
21
+ | Agent Card | Public JSON describing the agent: name, description, capabilities, and skills. Pinned on IPFS and linked from the agent URI; other agents fetch it for discovery. |
22
22
 
23
23
  </details>
24
24
 
@@ -48,12 +48,12 @@ Once running:
48
48
 
49
49
  The Identity Hub manages everything portable about the agent:
50
50
 
51
- - **Public Profile** edits name, description, icon, and the Agent Card.
51
+ - **Public Profile** edits name, description, and icon: what other agents see in the Agent Card.
52
52
  - **ENS Name** links the agent to a subdomain under a parent name the owner wallet controls.
53
53
  - **Custody Mode** switches between Simple and Advanced by depositing the token into its Vault or unwrapping it back out.
54
- - **Prepare Transfer** stages a dual-wallet snapshot before sending the token externally.
54
+ - **Prepare Transfer** stages a dual-wallet snapshot so the receiver can restore the agent after the token moves externally.
55
55
  - **Refetch Latest** pulls the most recent published snapshot back to local files.
56
- - **Load Agent** accepts either an ENS name or a bare token ID, and loads any agent owned by or linked to the connected wallet.
56
+ - **Switch Agent** accepts either an ENS name or a bare token ID, and loads any agent owned by or linked to the connected wallet.
57
57
 
58
58
  The menu surfaces drift automatically. Token ownership, vault state, ENS record alignment, and pending URI rotations are checked against the live chain when the menu opens.
59
59
 
@@ -61,16 +61,15 @@ Every agent has a continuity directory at `~/.ethagent/continuity`.
61
61
 
62
62
  ## Continuity
63
63
 
64
- Each agent's continuity directory holds a small set of files. Private files are encrypted before they ever reach IPFS; public files are plain JSON so other agents can discover what the agent does.
64
+ Each agent's continuity directory holds a small set of private files. They are encrypted before they ever reach IPFS.
65
65
 
66
66
  | File | Visibility | Purpose |
67
67
  | --- | --- | --- |
68
68
  | `SOUL.md` | Private | Soul, boundaries, standing instructions, and identity framing. |
69
69
  | `MEMORY.md` | Private | Durable preferences, project context, decisions, and operating notes. |
70
- | `skills/` | Private | Skill folders. The SKILL.md body never leaves your machine. The visibility flag only controls whether the skill's name and description get indexed in `skills.json`. New skills default to public. |
71
- | `skills.json` | Public | Machine-readable capabilities derived from public skills. |
70
+ | `skills/` | Private | Skill folders. The SKILL.md body never leaves your machine. The visibility flag only controls whether the skill's name and description get listed in the Agent Card. New skills default to public. |
72
71
 
73
- `SOUL.md`, `MEMORY.md`, and each `SKILL.md` are plain Markdown you edit through the Identity Hub under Continuity. Skill frontmatter (name, description, when_to_use, visibility, tags) tells the agent when to load it. The body stays local; `visibility: public` indexes the name and description in `skills.json` and the Agent Card.
72
+ `SOUL.md`, `MEMORY.md`, and each `SKILL.md` are plain Markdown you edit through the Identity Hub under Continuity. Skill frontmatter (name, description, when_to_use, visibility, tags) tells the agent when to load it. The body stays local; `visibility: public` lists the name and description in the Agent Card.
74
73
 
75
74
  - **Save Snapshot Now** encrypts the private files, pins them to IPFS, and rotates the onchain pointer to the new CID.
76
75
  - **Refetch Latest** reads the pointer back, signs the decrypt challenge with your wallet, and overwrites local files from the snapshot.
@@ -103,7 +102,7 @@ Save the token ID + network somewhere safe. ENS records can be cleared and rebui
103
102
  - Sender signs snapshot access, receiver signs restore access.
104
103
  - Sender publishes the snapshot pointer to the agent URI.
105
104
  - The actual transfer happens externally afterwards, in whichever wallet UI you prefer.
106
- - Once the token has moved, the receiver opens **Load Agent** with the receiving wallet and restores the same agent from the published snapshot.
105
+ - Once the token has moved, the receiver opens **Switch Agent** with the receiving wallet and restores the same agent from the published snapshot.
107
106
 
108
107
  The token transfer flow prepares decrypt access and agent URI pointers only. It does not initiate the transfer and does not request approval over the token.
109
108
 
@@ -136,8 +135,8 @@ Vision support is available on:
136
135
 
137
136
  ## Privacy
138
137
 
139
- - **Public:** token ownership, the agent URI payload, public discovery files, and IPFS CIDs.
140
- - **Private:** plaintext `SOUL.md`, plaintext `MEMORY.md`, sessions, prompt history, API keys, local permissions, and the wallet signatures used for decryption.
138
+ - **Public:** token ownership, the agent URI, the Agent Card it references, and IPFS CIDs.
139
+ - **Private:** plaintext `SOUL.md`, plaintext `MEMORY.md`, the local skills/ tree, sessions, prompt history, API keys, local permissions, and the wallet signatures used for decryption.
141
140
  - Snapshots use a wallet signature as unlock material. The signature does not submit a transaction, spend funds, or grant token approval.
142
141
  - The transfer flow writes a snapshot pointer and stops; it never approves or moves the token.
143
142
  - `ethagent reset` deletes local ethagent data from the current machine while preserving installed local model assets. It does not burn or transfer tokens, remove public IPFS content, or mutate the onchain agent URI. Run **Save Snapshot Now** before resetting if local edits should become the recoverable state.
@@ -150,7 +149,7 @@ Vision support is available on:
150
149
  | Model | Cloud provider or local GGUF runner. |
151
150
  | Identity | ERC-8004 token owned by the wallet. |
152
151
  | Continuity | Private files encrypted before IPFS pinning. |
153
- | Discovery | Public `skills.json`, Agent Card, services, and the current agent URI payload. |
152
+ | Discovery | The agent URI and the Agent Card it points to. |
154
153
  | Recovery | Refetch the current agent URI, decrypt the latest snapshot, and restore local files. |
155
154
 
156
155
  The ERC-8004 token is the durable handle. The machine, model, and local session all change around it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ethagent",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "A privacy-first AI agent with a portable Ethereum identity",
5
5
  "type": "module",
6
6
  "main": "bin/ethagent.js",
@@ -6,7 +6,7 @@ import type { ContinuityFiles } from './envelope.js'
6
6
  import {
7
7
  continuityVaultRef,
8
8
  ensureContinuityVault,
9
- writePublicSkillsFile,
9
+ writeAgentCardFile,
10
10
  writeContinuityFiles,
11
11
  type PrivateContinuityFile,
12
12
  } from './storage.js'
@@ -20,7 +20,7 @@ type PrivateContinuityHistorySnapshot = {
20
20
  existedBefore: boolean
21
21
  previousContent: string
22
22
  previousFiles?: ContinuityFiles
23
- previousPublicSkills?: string
23
+ previousAgentCard?: string
24
24
  changeSummary: string
25
25
  identity: {
26
26
  address: string
@@ -42,7 +42,7 @@ type RecordPrivateContinuityHistoryInput = {
42
42
  existedBefore: boolean
43
43
  previousContent: string
44
44
  previousFiles?: ContinuityFiles
45
- previousPublicSkills?: string
45
+ previousAgentCard?: string
46
46
  changeSummary: string
47
47
  createdAt?: string
48
48
  sessionId?: string
@@ -69,7 +69,7 @@ export async function recordPrivateContinuityHistorySnapshot(
69
69
  existedBefore: input.existedBefore,
70
70
  previousContent: input.previousContent,
71
71
  ...(input.previousFiles ? { previousFiles: input.previousFiles } : {}),
72
- ...(input.previousPublicSkills !== undefined ? { previousPublicSkills: input.previousPublicSkills } : {}),
72
+ ...(input.previousAgentCard !== undefined ? { previousAgentCard: input.previousAgentCard } : {}),
73
73
  changeSummary: input.changeSummary,
74
74
  identity: {
75
75
  address: input.identity.address,
@@ -125,8 +125,8 @@ export async function restorePrivateContinuityHistorySnapshot(
125
125
 
126
126
  if (snapshot.previousFiles) {
127
127
  await writeContinuityFiles(identity, snapshot.previousFiles)
128
- if (snapshot.previousPublicSkills !== undefined) {
129
- await writePublicSkillsFile(identity, snapshot.previousPublicSkills)
128
+ if (snapshot.previousAgentCard !== undefined) {
129
+ await writeAgentCardFile(identity, snapshot.previousAgentCard)
130
130
  }
131
131
  return snapshot
132
132
  }
@@ -137,8 +137,8 @@ export async function restorePrivateContinuityHistorySnapshot(
137
137
  } else {
138
138
  await fs.rm(snapshot.filePath, { force: true })
139
139
  }
140
- if (snapshot.previousPublicSkills !== undefined) {
141
- await writePublicSkillsFile(identity, snapshot.previousPublicSkills)
140
+ if (snapshot.previousAgentCard !== undefined) {
141
+ await writeAgentCardFile(identity, snapshot.previousAgentCard)
142
142
  }
143
143
  return snapshot
144
144
  }
@@ -68,29 +68,7 @@ export function defaultPublicSkillsProfile(identity: EthagentIdentity): PublicSk
68
68
  version: '1.0.0',
69
69
  ...(agentWallet ? { agentWallet } : {}),
70
70
  ...(imageUrl ? { imageUrl } : {}),
71
- skills: [
72
- {
73
- id: 'software-engineering',
74
- name: 'Software engineering',
75
- description: 'Read code, plan implementations, debug failures, refactor safely, and run focused tests.',
76
- inputModes: ['text/markdown'],
77
- outputModes: ['text/markdown'],
78
- },
79
- {
80
- id: 'workspace-tools',
81
- name: 'Workspace tools',
82
- description: 'Use permissioned local file, shell, clipboard, and MCP tools for project work.',
83
- inputModes: ['text/markdown'],
84
- outputModes: ['text/markdown', 'application/json'],
85
- },
86
- {
87
- id: 'ethereum-identity',
88
- name: 'Ethereum identity',
89
- description: 'Represent a portable ERC-8004 agent identity controlled by the owner wallet.',
90
- inputModes: ['text/markdown'],
91
- outputModes: ['text/markdown', 'application/json'],
92
- },
93
- ],
71
+ skills: [],
94
72
  }
95
73
  }
96
74
 
@@ -129,61 +107,6 @@ function uniqueSkillId(base: string, used: Set<string>): string {
129
107
  return `${slug}-${i}`
130
108
  }
131
109
 
132
- export function renderPublicSkillsJson(profile: PublicSkillsProfile): string {
133
- const inputModes = unique(profile.skills.flatMap(skill => skill.inputModes))
134
- const outputModes = unique(profile.skills.flatMap(skill => skill.outputModes))
135
- const summary = {
136
- schema: 'ethagent.public-skills.v1',
137
- producer: ETHAGENT_PRODUCER,
138
- ...(profile.agentWallet ? { agent_wallet: profile.agentWallet } : {}),
139
- visibility: 'public',
140
- name: profile.name,
141
- description: profile.description,
142
- version: profile.version,
143
- ...(profile.imageUrl ? { imageUrl: profile.imageUrl } : {}),
144
- inputModes,
145
- outputModes,
146
- boundary: 'Public discovery only. This is not executable code, private memory, or a skill installation manifest.',
147
- capabilities: {
148
- softwareEngineering: true,
149
- workspaceTools: 'permissioned',
150
- mcp: true,
151
- streaming: true,
152
- ethereumIdentity: 'ERC-8004',
153
- encryptedContinuity: true,
154
- },
155
- delegation: {
156
- bestFor: [
157
- 'code reading',
158
- 'implementation planning',
159
- 'debugging',
160
- 'refactors',
161
- 'tests',
162
- 'workspace automation',
163
- ],
164
- requiresApprovalFor: [
165
- 'workspace edits',
166
- 'shell commands',
167
- 'private continuity changes',
168
- ],
169
- },
170
- privacy: {
171
- publicOnly: true,
172
- includesPrivateMemory: false,
173
- includesExecutableCode: false,
174
- includesSecrets: false,
175
- },
176
- skills: profile.skills.map(skill => ({
177
- id: skill.id,
178
- name: skill.name,
179
- description: skill.description,
180
- inputModes: skill.inputModes,
181
- outputModes: skill.outputModes,
182
- })),
183
- }
184
- return `${JSON.stringify(summary, null, 2)}\n`
185
- }
186
-
187
110
  export function createAgentCard(profile: PublicSkillsProfile, url?: string): AgentCard {
188
111
  const inputModes = unique(profile.skills.flatMap(skill => skill.inputModes))
189
112
  const outputModes = unique(profile.skills.flatMap(skill => skill.outputModes))
@@ -192,7 +115,7 @@ export function createAgentCard(profile: PublicSkillsProfile, url?: string): Age
192
115
  description: profile.description,
193
116
  version: profile.version,
194
117
  ...(profile.agentWallet ? { agent_wallet: profile.agentWallet } : {}),
195
- protocolVersion: '0.2.6',
118
+ protocolVersion: '0.3.0',
196
119
  ...(url ? { url } : {}),
197
120
  ...(profile.imageUrl ? { image: profile.imageUrl } : {}),
198
121
  defaultInputModes: inputModes.length ? inputModes : ['text/markdown'],
@@ -2,8 +2,9 @@ import { atomicWriteText } from '../../../storage/atomicWrite.js'
2
2
  import type { EthagentIdentity } from '../../../storage/config.js'
3
3
  import {
4
4
  appendPublicSkillEntries,
5
+ createAgentCard,
5
6
  defaultPublicSkillsProfile,
6
- renderPublicSkillsJson,
7
+ serializeAgentCard,
7
8
  } from '../publicSkills.js'
8
9
  import { ensureContinuityVault, ensureTrailingNewline, readOrDefault } from '../storage/files.js'
9
10
  import { listSkills } from './loadSkills.js'
@@ -14,19 +15,19 @@ export async function derivePublicSkillEntries(identity: EthagentIdentity): Prom
14
15
  return entries.filter(entry => entry.visibility === 'public')
15
16
  }
16
17
 
17
- export async function renderPublicSkillsJsonForIdentity(identity: EthagentIdentity): Promise<string> {
18
+ export async function renderAgentCardJsonForIdentity(identity: EthagentIdentity): Promise<string> {
18
19
  const publicEntries = await derivePublicSkillEntries(identity)
19
20
  const profile = appendPublicSkillEntries(defaultPublicSkillsProfile(identity), publicEntries)
20
- return renderPublicSkillsJson(profile)
21
+ return serializeAgentCard(createAgentCard(profile))
21
22
  }
22
23
 
23
- export async function syncPublicSkillsManifest(identity: EthagentIdentity): Promise<string> {
24
+ export async function syncAgentCardManifest(identity: EthagentIdentity): Promise<string> {
24
25
  const ref = await ensureContinuityVault(identity)
25
- const next = await renderPublicSkillsJsonForIdentity(identity)
26
- const current = await readOrDefault(ref.publicSkillsPath, '')
26
+ const next = await renderAgentCardJsonForIdentity(identity)
27
+ const current = await readOrDefault(ref.agentCardPath, '')
27
28
  if (current === ensureTrailingNewline(next) || current === next) {
28
29
  return current
29
30
  }
30
- await atomicWriteText(ref.publicSkillsPath, ensureTrailingNewline(next), { mode: 0o644 })
31
+ await atomicWriteText(ref.agentCardPath, ensureTrailingNewline(next), { mode: 0o644 })
31
32
  return next
32
33
  }
@@ -17,7 +17,6 @@ type PublishedContinuitySnapshot = {
17
17
  metadataCid?: string
18
18
  agentUri?: string
19
19
  txHash?: string
20
- publicSkillsCid?: string
21
20
  agentCardCid?: string
22
21
  contentHashes?: ContinuitySnapshotContentHashes
23
22
  label: string
@@ -55,8 +54,7 @@ export async function recordPublishedContinuitySnapshot(
55
54
  ...(backup.metadataCid ? { metadataCid: backup.metadataCid } : {}),
56
55
  ...(backup.agentUri ? { agentUri: backup.agentUri } : {}),
57
56
  ...(backup.txHash ? { txHash: backup.txHash } : {}),
58
- ...(input.identity.publicSkills?.cid ? { publicSkillsCid: input.identity.publicSkills.cid } : {}),
59
- ...(input.identity.publicSkills?.agentCardCid ? { agentCardCid: input.identity.publicSkills.agentCardCid } : {}),
57
+ ...(input.identity.agentCard?.cid ? { agentCardCid: input.identity.agentCard.cid } : {}),
60
58
  ...(contentHashes ? { contentHashes } : {}),
61
59
  label: input.label ?? 'published encrypted snapshot',
62
60
  identity: {
@@ -129,7 +127,6 @@ function enrichPublishedSnapshot(
129
127
  ...(snapshot.metadataCid ? {} : current.metadataCid ? { metadataCid: current.metadataCid } : {}),
130
128
  ...(snapshot.agentUri ? {} : current.agentUri ? { agentUri: current.agentUri } : {}),
131
129
  ...(snapshot.txHash ? {} : current.txHash ? { txHash: current.txHash } : {}),
132
- ...(snapshot.publicSkillsCid ? {} : current.publicSkillsCid ? { publicSkillsCid: current.publicSkillsCid } : {}),
133
130
  ...(snapshot.agentCardCid ? {} : current.agentCardCid ? { agentCardCid: current.agentCardCid } : {}),
134
131
  ...(snapshot.contentHashes ? {} : current.contentHashes ? { contentHashes: current.contentHashes } : {}),
135
132
  }
@@ -141,8 +138,7 @@ function refreshPublishedSnapshotSidecars(
141
138
  ): PublishedContinuitySnapshot {
142
139
  return {
143
140
  ...snapshot,
144
- ...(identity.publicSkills?.cid ? { publicSkillsCid: identity.publicSkills.cid } : {}),
145
- ...(identity.publicSkills?.agentCardCid ? { agentCardCid: identity.publicSkills.agentCardCid } : {}),
141
+ ...(identity.agentCard?.cid ? { agentCardCid: identity.agentCard.cid } : {}),
146
142
  }
147
143
  }
148
144
 
@@ -180,8 +176,7 @@ function currentPublishedSnapshot(identity: EthagentIdentity): PublishedContinui
180
176
  ...(backup.metadataCid ? { metadataCid: backup.metadataCid } : {}),
181
177
  ...(backup.agentUri ? { agentUri: backup.agentUri } : {}),
182
178
  ...(backup.txHash ? { txHash: backup.txHash } : {}),
183
- ...(identity.publicSkills?.cid ? { publicSkillsCid: identity.publicSkills.cid } : {}),
184
- ...(identity.publicSkills?.agentCardCid ? { agentCardCid: identity.publicSkills.agentCardCid } : {}),
179
+ ...(identity.agentCard?.cid ? { agentCardCid: identity.agentCard.cid } : {}),
185
180
  label: 'current published snapshot',
186
181
  identity: {
187
182
  address: identity.address,
@@ -1,6 +1,6 @@
1
1
  import type { EthagentIdentity } from '../../../storage/config.js'
2
2
  import type { ContinuityAgentSnapshot, ContinuityFiles } from '../envelope.js'
3
- import { defaultPublicSkillsProfile, renderPublicSkillsJson } from '../publicSkills.js'
3
+ import { createAgentCard, defaultPublicSkillsProfile, serializeAgentCard } from '../publicSkills.js'
4
4
  import { renderPrivateIdentityBlock } from './markdown.js'
5
5
 
6
6
  export function continuityAgentSnapshot(identity: EthagentIdentity): ContinuityAgentSnapshot {
@@ -87,6 +87,6 @@ export function defaultContinuityFiles(identity: EthagentIdentity, _now = new Da
87
87
  }
88
88
  }
89
89
 
90
- export function defaultPublicSkillsJson(identity: EthagentIdentity): string {
91
- return renderPublicSkillsJson(defaultPublicSkillsProfile(identity))
90
+ export function defaultAgentCardJson(identity: EthagentIdentity): string {
91
+ return serializeAgentCard(createAgentCard(defaultPublicSkillsProfile(identity)))
92
92
  }
@@ -8,7 +8,7 @@ export function continuityVaultRef(identity: Pick<EthagentIdentity, 'chainId' |
8
8
  dir,
9
9
  soulPath: path.join(dir, 'SOUL.md'),
10
10
  memoryPath: path.join(dir, 'MEMORY.md'),
11
- publicSkillsPath: path.join(dir, 'skills.json'),
11
+ agentCardPath: path.join(dir, 'agent-card.json'),
12
12
  skillsDir: path.join(dir, 'skills'),
13
13
  }
14
14
  }
@@ -1,3 +1,5 @@
1
+ import fs from 'node:fs/promises'
2
+ import path from 'node:path'
1
3
  import { atomicWriteText } from '../../../storage/atomicWrite.js'
2
4
  import type { EthagentIdentity } from '../../../storage/config.js'
3
5
  import type { ContinuityFiles, ContinuitySkillsTree } from '../envelope.js'
@@ -6,10 +8,10 @@ import {
6
8
  materializeSkillsTree,
7
9
  } from '../skills/loadSkills.js'
8
10
  import {
9
- renderPublicSkillsJsonForIdentity,
10
- syncPublicSkillsManifest,
11
+ renderAgentCardJsonForIdentity,
12
+ syncAgentCardManifest,
11
13
  } from '../skills/publicSkillsSync.js'
12
- import { defaultContinuityFiles, defaultPublicSkillsJson } from './defaults.js'
14
+ import { defaultContinuityFiles, defaultAgentCardJson } from './defaults.js'
13
15
  import {
14
16
  ensureContinuityFiles,
15
17
  ensureContinuityVault,
@@ -24,14 +26,14 @@ import type { ContinuityVaultRef, IdentityMarkdownScaffold } from './types.js'
24
26
 
25
27
  export async function ensureIdentityMarkdownScaffold(
26
28
  identity: EthagentIdentity,
27
- options: { publicSkillsFallback?: string | (() => Promise<string>) } = {},
29
+ options: { agentCardFallback?: string | (() => Promise<string>) } = {},
28
30
  ): Promise<IdentityMarkdownScaffold> {
29
31
  const privateFiles = await ensureContinuityFiles(identity)
30
- const publicSkills = await ensurePublicSkillsFile(identity, { fallback: options.publicSkillsFallback })
31
- const syncedPublic = await syncPublicSkillsManifest(identity).catch(() => publicSkills)
32
+ const agentCard = await ensureAgentCardFile(identity, { fallback: options.agentCardFallback })
33
+ const syncedCard = await syncAgentCardManifest(identity).catch(() => agentCard)
32
34
  return {
33
35
  ...privateFiles,
34
- 'skills.json': syncedPublic,
36
+ 'agent-card.json': syncedCard,
35
37
  }
36
38
  }
37
39
 
@@ -43,7 +45,7 @@ export async function writeIdentityMarkdownScaffold(
43
45
  'SOUL.md': files['SOUL.md'],
44
46
  'MEMORY.md': files['MEMORY.md'],
45
47
  })
46
- await writePublicSkillsFile(identity, files['skills.json'])
48
+ await writeAgentCardFile(identity, files['agent-card.json'])
47
49
  return ref
48
50
  }
49
51
 
@@ -57,7 +59,7 @@ export async function prepareSyncedIdentityMarkdownScaffold(identity: EthagentId
57
59
  await ensureIdentityMarkdownScaffold(identity)
58
60
  const privateFiles = await readContinuityFiles(identity)
59
61
  const privateDefaults = defaultContinuityFiles(identity)
60
- const publicDefault = await renderPublicSkillsJsonForIdentity(identity)
62
+ const agentCardDefault = await renderAgentCardJsonForIdentity(identity)
61
63
  return {
62
64
  'SOUL.md': syncGeneratedMarkdown(privateFiles['SOUL.md'], privateDefaults['SOUL.md'], [
63
65
  { marker: 'identity' },
@@ -65,13 +67,13 @@ export async function prepareSyncedIdentityMarkdownScaffold(identity: EthagentId
65
67
  'MEMORY.md': syncGeneratedMarkdown(privateFiles['MEMORY.md'], privateDefaults['MEMORY.md'], [
66
68
  { marker: 'identity' },
67
69
  ]),
68
- 'skills.json': publicDefault,
70
+ 'agent-card.json': agentCardDefault,
69
71
  }
70
72
  }
71
73
 
72
- export async function prepareSyncedPublicSkillsJson(identity: EthagentIdentity): Promise<string> {
73
- await ensurePublicSkillsFile(identity)
74
- return renderPublicSkillsJsonForIdentity(identity)
74
+ export async function prepareSyncedAgentCardJson(identity: EthagentIdentity): Promise<string> {
75
+ await ensureAgentCardFile(identity)
76
+ return renderAgentCardJsonForIdentity(identity)
75
77
  }
76
78
 
77
79
  export async function prepareSyncedSkillsTree(identity: EthagentIdentity): Promise<ContinuitySkillsTree> {
@@ -87,30 +89,40 @@ export async function restoreSkillsTree(
87
89
  }
88
90
 
89
91
 
90
- export async function ensurePublicSkillsFile(
92
+ export async function ensureAgentCardFile(
91
93
  identity: EthagentIdentity,
92
94
  options: { fallback?: string | (() => Promise<string>) } = {},
93
95
  ): Promise<string> {
94
96
  const ref = await ensureContinuityVault(identity)
95
- if (await exists(ref.publicSkillsPath)) return readPublicSkillsFile(identity)
97
+ if (await exists(ref.agentCardPath)) return readAgentCardFile(identity)
96
98
 
97
- const fallback = await resolvePublicSkillsFallback(identity, options.fallback)
98
- await atomicWriteText(ref.publicSkillsPath, ensureTrailingNewline(fallback), { mode: 0o644 })
99
- return readPublicSkillsFile(identity)
99
+ const legacyPath = path.join(ref.dir, 'skills.json')
100
+ if (await exists(legacyPath)) {
101
+ const legacyContent = await readOrDefault(legacyPath, '')
102
+ if (legacyContent) {
103
+ await atomicWriteText(ref.agentCardPath, ensureTrailingNewline(legacyContent), { mode: 0o644 })
104
+ }
105
+ await fs.rm(legacyPath, { force: true })
106
+ if (await exists(ref.agentCardPath)) return readAgentCardFile(identity)
107
+ }
108
+
109
+ const fallback = await resolveAgentCardFallback(identity, options.fallback)
110
+ await atomicWriteText(ref.agentCardPath, ensureTrailingNewline(fallback), { mode: 0o644 })
111
+ return readAgentCardFile(identity)
100
112
  }
101
113
 
102
- export async function readPublicSkillsFile(identity: EthagentIdentity): Promise<string> {
114
+ export async function readAgentCardFile(identity: EthagentIdentity): Promise<string> {
103
115
  const ref = await ensureContinuityVault(identity)
104
- return readOrDefault(ref.publicSkillsPath, defaultPublicSkillsJson(identity))
116
+ return readOrDefault(ref.agentCardPath, defaultAgentCardJson(identity))
105
117
  }
106
118
 
107
- export async function writePublicSkillsFile(identity: EthagentIdentity, content: string): Promise<ContinuityVaultRef> {
119
+ export async function writeAgentCardFile(identity: EthagentIdentity, content: string): Promise<ContinuityVaultRef> {
108
120
  const ref = await ensureContinuityVault(identity)
109
- await atomicWriteText(ref.publicSkillsPath, ensureTrailingNewline(content), { mode: 0o644 })
121
+ await atomicWriteText(ref.agentCardPath, ensureTrailingNewline(content), { mode: 0o644 })
110
122
  return ref
111
123
  }
112
124
 
113
- async function resolvePublicSkillsFallback(
125
+ async function resolveAgentCardFallback(
114
126
  identity: EthagentIdentity,
115
127
  fallback: string | (() => Promise<string>) | undefined,
116
128
  ): Promise<string> {
@@ -119,8 +131,8 @@ async function resolvePublicSkillsFallback(
119
131
  try {
120
132
  return await fallback()
121
133
  } catch {
122
- return defaultPublicSkillsJson(identity)
134
+ return defaultAgentCardJson(identity)
123
135
  }
124
136
  }
125
- return defaultPublicSkillsJson(identity)
137
+ return defaultAgentCardJson(identity)
126
138
  }
@@ -2,10 +2,10 @@ import { createHash } from 'node:crypto'
2
2
  import type { EthagentIdentity } from '../../../storage/config.js'
3
3
  import type { ContinuityFiles, ContinuitySkillsTree } from '../envelope.js'
4
4
  import { loadSkillsTree } from '../skills/loadSkills.js'
5
- import { syncPublicSkillsManifest } from '../skills/publicSkillsSync.js'
5
+ import { syncAgentCardManifest } from '../skills/publicSkillsSync.js'
6
6
  import { continuityVaultRef } from './paths.js'
7
7
  import { exists, readContinuityFiles, statIfExists } from './files.js'
8
- import { readPublicSkillsFile } from './scaffold.js'
8
+ import { readAgentCardFile } from './scaffold.js'
9
9
  import type { ContinuityPublishState, ContinuitySnapshotContentHashes, ContinuityVaultRef, ContinuityWorkingTreeStatus } from './types.js'
10
10
 
11
11
  export async function continuityVaultStatus(identity: EthagentIdentity): Promise<{ ready: boolean; files: ContinuityVaultRef }> {
@@ -22,7 +22,7 @@ export async function continuityWorkingTreeStatus(
22
22
  const stats = await Promise.all([
23
23
  statIfExists(ref.soulPath),
24
24
  statIfExists(ref.memoryPath),
25
- statIfExists(ref.publicSkillsPath),
25
+ statIfExists(ref.agentCardPath),
26
26
  ])
27
27
  const newestMs = Math.max(0, ...stats.flatMap(stat => stat ? [stat.mtimeMs] : []))
28
28
  const ready = Boolean(stats[0] && stats[1])
@@ -54,30 +54,30 @@ export async function localContinuitySnapshotContentHashes(
54
54
  identity: EthagentIdentity,
55
55
  ): Promise<ContinuitySnapshotContentHashes> {
56
56
  const privateFiles = await readContinuityFiles(identity)
57
- await syncPublicSkillsManifest(identity).catch(() => undefined)
58
- const publicSkills = await readPublicSkillsFile(identity)
57
+ await syncAgentCardManifest(identity).catch(() => undefined)
58
+ const agentCard = await readAgentCardFile(identity)
59
59
  const skills = await loadSkillsTree(identity).catch(() => ({} as ContinuitySkillsTree))
60
- return continuitySnapshotContentHashes(privateFiles, publicSkills, skills)
60
+ return continuitySnapshotContentHashes(privateFiles, agentCard, skills)
61
61
  }
62
62
 
63
63
  export function continuitySnapshotContentHashesFromSources(args: {
64
64
  privateFiles: ContinuityFiles
65
- publicSkills: string
65
+ agentCard: string
66
66
  skills: ContinuitySkillsTree
67
67
  }): ContinuitySnapshotContentHashes {
68
- return continuitySnapshotContentHashes(args.privateFiles, args.publicSkills, args.skills)
68
+ return continuitySnapshotContentHashes(args.privateFiles, args.agentCard, args.skills)
69
69
  }
70
70
 
71
71
  function continuitySnapshotContentHashes(
72
72
  privateFiles: ContinuityFiles,
73
- publicSkills: string,
73
+ agentCard: string,
74
74
  skills: ContinuitySkillsTree,
75
75
  ): ContinuitySnapshotContentHashes {
76
76
  const skillsHash = hashSkillsTree(skills)
77
77
  return {
78
78
  'SOUL.md': hashContinuitySnapshotContent(privateFiles['SOUL.md']),
79
79
  'MEMORY.md': hashContinuitySnapshotContent(privateFiles['MEMORY.md']),
80
- 'skills.json': hashContinuitySnapshotContent(publicSkills),
80
+ 'agent-card.json': hashContinuitySnapshotContent(agentCard),
81
81
  ...(skillsHash ? { 'private-skills': skillsHash } : {}),
82
82
  }
83
83
  }
@@ -101,7 +101,7 @@ function equalContinuitySnapshotHashes(
101
101
  ): boolean {
102
102
  if (a['SOUL.md'] !== b['SOUL.md']) return false
103
103
  if (a['MEMORY.md'] !== b['MEMORY.md']) return false
104
- if (a['skills.json'] !== b['skills.json']) return false
104
+ if (a['agent-card.json'] !== b['agent-card.json']) return false
105
105
  return a['private-skills'] === b['private-skills']
106
106
  }
107
107
 
@@ -7,16 +7,16 @@ export type ContinuityVaultRef = {
7
7
  dir: string
8
8
  soulPath: string
9
9
  memoryPath: string
10
- publicSkillsPath: string
10
+ agentCardPath: string
11
11
  skillsDir: string
12
12
  }
13
13
 
14
14
  export type IdentityMarkdownScaffold = ContinuityFiles & {
15
- 'skills.json': string
15
+ 'agent-card.json': string
16
16
  }
17
17
 
18
- export type ContinuitySnapshotFile = PrivateContinuityFile | 'skills.json' | 'private-skills'
19
- export type ContinuitySnapshotContentHashes = Partial<Record<ContinuitySnapshotFile, string>> & Record<PrivateContinuityFile | 'skills.json', string>
18
+ export type ContinuitySnapshotFile = PrivateContinuityFile | 'agent-card.json' | 'private-skills'
19
+ export type ContinuitySnapshotContentHashes = Partial<Record<ContinuitySnapshotFile, string>> & Record<PrivateContinuityFile | 'agent-card.json', string>
20
20
  export type ContinuityPublishState = 'not-restored' | 'not-published' | 'verify-needed' | 'local-changes' | 'published'
21
21
  export type ContinuityWorkingTreeStatus = {
22
22
  ready: boolean
@@ -17,15 +17,15 @@ export {
17
17
  export { continuityAgentSnapshot, defaultContinuityFiles } from './storage/defaults.js'
18
18
  export {
19
19
  ensureIdentityMarkdownScaffold,
20
- ensurePublicSkillsFile,
20
+ ensureAgentCardFile,
21
21
  prepareSyncedIdentityMarkdownScaffold,
22
22
  prepareSyncedSkillsTree,
23
- prepareSyncedPublicSkillsJson,
24
- readPublicSkillsFile,
23
+ prepareSyncedAgentCardJson,
24
+ readAgentCardFile,
25
25
  restoreSkillsTree,
26
26
  syncIdentityMarkdownScaffold,
27
27
  writeIdentityMarkdownScaffold,
28
- writePublicSkillsFile,
28
+ writeAgentCardFile,
29
29
  } from './storage/scaffold.js'
30
30
  export {
31
31
  continuityVaultStatus,