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.
- package/README.md +15 -16
- package/package.json +1 -1
- package/src/identity/continuity/history.ts +8 -8
- package/src/identity/continuity/publicSkills.ts +2 -79
- package/src/identity/continuity/skills/publicSkillsSync.ts +8 -7
- package/src/identity/continuity/snapshots.ts +3 -8
- package/src/identity/continuity/storage/defaults.ts +3 -3
- package/src/identity/continuity/storage/paths.ts +1 -1
- package/src/identity/continuity/storage/scaffold.ts +37 -25
- package/src/identity/continuity/storage/status.ts +11 -11
- package/src/identity/continuity/storage/types.ts +4 -4
- package/src/identity/continuity/storage.ts +4 -4
- package/src/identity/ens/agentRecords.ts +61 -45
- package/src/identity/ens/ensAutomation/read.ts +7 -10
- package/src/identity/ens/ensAutomation/setup.ts +10 -16
- package/src/identity/ens/ensAutomation/types.ts +0 -1
- package/src/identity/ens/ensAutomation.ts +1 -0
- package/src/identity/ens/ensLookup/records.ts +1 -1
- package/src/identity/ens/ensLookup.ts +1 -1
- package/src/identity/ens/erc7930.ts +48 -0
- package/src/identity/hub/OperationalRoutes.tsx +4 -1
- package/src/identity/hub/continuity/effects.ts +17 -39
- package/src/identity/hub/continuity/skills/NewSkillVisibilityScreen.tsx +2 -2
- package/src/identity/hub/continuity/skills/SkillActionsScreen.tsx +2 -2
- package/src/identity/hub/continuity/state.ts +1 -1
- package/src/identity/hub/continuity/vault.ts +16 -50
- package/src/identity/hub/create/effects.ts +12 -16
- package/src/identity/hub/ens/EnsEditFlow.tsx +19 -5
- package/src/identity/hub/ens/EnsEditReviewScreens.tsx +11 -11
- package/src/identity/hub/ens/EnsEditShared.tsx +2 -6
- package/src/identity/hub/ens/editCopy.ts +1 -3
- package/src/identity/hub/ens/transactions.ts +67 -18
- package/src/identity/hub/profile/effects.ts +15 -30
- package/src/identity/hub/profile/identity.ts +2 -4
- package/src/identity/hub/profile/operatorSave.ts +10 -30
- package/src/identity/hub/restore/RestoreFlow.tsx +9 -9
- package/src/identity/hub/restore/apply.ts +7 -8
- package/src/identity/hub/restore/helpers.ts +3 -3
- package/src/identity/hub/restore/recovery.ts +9 -10
- package/src/identity/hub/restore/resolve.ts +11 -9
- package/src/identity/hub/shared/components/MenuScreen.tsx +3 -3
- package/src/identity/hub/shared/components/UnlinkedIdentityScreen.tsx +2 -2
- package/src/identity/hub/shared/effects/sync.ts +1 -1
- package/src/identity/hub/transfer/TokenTransferScreens.tsx +1 -1
- package/src/identity/hub/transfer/effects.ts +10 -31
- package/src/identity/hub/useIdentityHubContinuity.ts +12 -12
- package/src/identity/hub/useIdentityHubController.ts +12 -3
- package/src/identity/registry/erc8004/metadata.ts +10 -27
- package/src/identity/registry/erc8004/types.ts +0 -1
- package/src/storage/config.ts +1 -2
- package/src/storage/identity.ts +3 -3
- package/src/storage/rewind.ts +1 -1
- package/src/tools/privateContinuityEditTool.ts +4 -4
- 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
|
|
9
|
-
- **Public.** The agent URI points to
|
|
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
|
|
21
|
-
| Agent Card | Public JSON describing the agent: name, description, capabilities, and skills.
|
|
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
|
|
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
|
|
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
|
-
- **
|
|
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.
|
|
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
|
|
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`
|
|
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 **
|
|
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
|
|
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 |
|
|
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
|
@@ -6,7 +6,7 @@ import type { ContinuityFiles } from './envelope.js'
|
|
|
6
6
|
import {
|
|
7
7
|
continuityVaultRef,
|
|
8
8
|
ensureContinuityVault,
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
129
|
-
await
|
|
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.
|
|
141
|
-
await
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
21
|
+
return serializeAgentCard(createAgentCard(profile))
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
export async function
|
|
24
|
+
export async function syncAgentCardManifest(identity: EthagentIdentity): Promise<string> {
|
|
24
25
|
const ref = await ensureContinuityVault(identity)
|
|
25
|
-
const next = await
|
|
26
|
-
const current = await readOrDefault(ref.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
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
|
|
91
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
+
renderAgentCardJsonForIdentity,
|
|
12
|
+
syncAgentCardManifest,
|
|
11
13
|
} from '../skills/publicSkillsSync.js'
|
|
12
|
-
import { defaultContinuityFiles,
|
|
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: {
|
|
29
|
+
options: { agentCardFallback?: string | (() => Promise<string>) } = {},
|
|
28
30
|
): Promise<IdentityMarkdownScaffold> {
|
|
29
31
|
const privateFiles = await ensureContinuityFiles(identity)
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
+
const agentCard = await ensureAgentCardFile(identity, { fallback: options.agentCardFallback })
|
|
33
|
+
const syncedCard = await syncAgentCardManifest(identity).catch(() => agentCard)
|
|
32
34
|
return {
|
|
33
35
|
...privateFiles,
|
|
34
|
-
'
|
|
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
|
|
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
|
|
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
|
-
'
|
|
70
|
+
'agent-card.json': agentCardDefault,
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
export async function
|
|
73
|
-
await
|
|
74
|
-
return
|
|
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
|
|
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.
|
|
97
|
+
if (await exists(ref.agentCardPath)) return readAgentCardFile(identity)
|
|
96
98
|
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
114
|
+
export async function readAgentCardFile(identity: EthagentIdentity): Promise<string> {
|
|
103
115
|
const ref = await ensureContinuityVault(identity)
|
|
104
|
-
return readOrDefault(ref.
|
|
116
|
+
return readOrDefault(ref.agentCardPath, defaultAgentCardJson(identity))
|
|
105
117
|
}
|
|
106
118
|
|
|
107
|
-
export async function
|
|
119
|
+
export async function writeAgentCardFile(identity: EthagentIdentity, content: string): Promise<ContinuityVaultRef> {
|
|
108
120
|
const ref = await ensureContinuityVault(identity)
|
|
109
|
-
await atomicWriteText(ref.
|
|
121
|
+
await atomicWriteText(ref.agentCardPath, ensureTrailingNewline(content), { mode: 0o644 })
|
|
110
122
|
return ref
|
|
111
123
|
}
|
|
112
124
|
|
|
113
|
-
async function
|
|
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
|
|
134
|
+
return defaultAgentCardJson(identity)
|
|
123
135
|
}
|
|
124
136
|
}
|
|
125
|
-
return
|
|
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 {
|
|
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 {
|
|
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.
|
|
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
|
|
58
|
-
const
|
|
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,
|
|
60
|
+
return continuitySnapshotContentHashes(privateFiles, agentCard, skills)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export function continuitySnapshotContentHashesFromSources(args: {
|
|
64
64
|
privateFiles: ContinuityFiles
|
|
65
|
-
|
|
65
|
+
agentCard: string
|
|
66
66
|
skills: ContinuitySkillsTree
|
|
67
67
|
}): ContinuitySnapshotContentHashes {
|
|
68
|
-
return continuitySnapshotContentHashes(args.privateFiles, args.
|
|
68
|
+
return continuitySnapshotContentHashes(args.privateFiles, args.agentCard, args.skills)
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
function continuitySnapshotContentHashes(
|
|
72
72
|
privateFiles: ContinuityFiles,
|
|
73
|
-
|
|
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
|
-
'
|
|
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['
|
|
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
|
-
|
|
10
|
+
agentCardPath: string
|
|
11
11
|
skillsDir: string
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export type IdentityMarkdownScaffold = ContinuityFiles & {
|
|
15
|
-
'
|
|
15
|
+
'agent-card.json': string
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export type ContinuitySnapshotFile = PrivateContinuityFile | '
|
|
19
|
-
export type ContinuitySnapshotContentHashes = Partial<Record<ContinuitySnapshotFile, string>> & Record<PrivateContinuityFile | '
|
|
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
|
-
|
|
20
|
+
ensureAgentCardFile,
|
|
21
21
|
prepareSyncedIdentityMarkdownScaffold,
|
|
22
22
|
prepareSyncedSkillsTree,
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
prepareSyncedAgentCardJson,
|
|
24
|
+
readAgentCardFile,
|
|
25
25
|
restoreSkillsTree,
|
|
26
26
|
syncIdentityMarkdownScaffold,
|
|
27
27
|
writeIdentityMarkdownScaffold,
|
|
28
|
-
|
|
28
|
+
writeAgentCardFile,
|
|
29
29
|
} from './storage/scaffold.js'
|
|
30
30
|
export {
|
|
31
31
|
continuityVaultStatus,
|