ethagent 3.0.1 → 3.1.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 +6 -1
- package/package.json +3 -1
- package/src/app/FirstRun.tsx +1 -24
- package/src/app/firstRunConfig.ts +26 -0
- package/src/auth/openaiOAuth/landingPage.ts +2 -11
- package/src/chat/ChatScreen.tsx +32 -117
- package/src/chat/MessageList.tsx +18 -260
- package/src/chat/chatEnvironment.ts +16 -0
- package/src/chat/chatTurnContext.ts +50 -0
- package/src/chat/chatTurnOrchestrator.ts +5 -112
- package/src/chat/chatTurnRows.ts +64 -0
- package/src/chat/commands.ts +3 -178
- package/src/chat/continuityEditReview.ts +42 -0
- package/src/chat/input/ChatInput.tsx +10 -144
- package/src/chat/input/chatInputHelpers.ts +62 -0
- package/src/chat/input/inputRendering.tsx +93 -0
- package/src/chat/messageMarkdown.ts +220 -0
- package/src/chat/messageRows.ts +43 -0
- package/src/chat/planImplementation.ts +62 -0
- package/src/chat/slashCommandHandlers.ts +165 -0
- package/src/chat/slashCommandViews.ts +120 -0
- package/src/cli/main.tsx +7 -0
- package/src/identity/continuity/challenges.ts +123 -0
- package/src/identity/continuity/envelope.ts +49 -1484
- package/src/identity/continuity/envelopeCreate.ts +322 -0
- package/src/identity/continuity/envelopeCrypto.ts +182 -0
- package/src/identity/continuity/envelopeParse.ts +441 -0
- package/src/identity/continuity/envelopeTypes.ts +204 -0
- package/src/identity/continuity/envelopeVersion.ts +1 -0
- package/src/identity/continuity/payloadNormalization.ts +183 -0
- package/src/identity/continuity/publicSkills.ts +5 -5
- package/src/identity/continuity/skills/loadSkills.ts +12 -69
- package/src/identity/continuity/skills/skillPaths.ts +76 -0
- package/src/identity/continuity/skillsNormalization.ts +119 -0
- package/src/identity/continuity/snapshotToken.ts +28 -0
- package/src/identity/hub/continuity/completion.ts +67 -0
- package/src/identity/hub/continuity/effects.ts +5 -62
- package/src/identity/hub/profile/effects.ts +6 -170
- package/src/identity/hub/profile/operatorSave.ts +202 -0
- package/src/identity/registry/erc8004/metadata.ts +31 -23
- package/src/identity/wallet/browserWallet/html.ts +1 -57
- package/src/identity/wallet/browserWallet/walletPageSource.ts +85 -0
- package/src/identity/wallet/page/controller.ts +1 -1
- package/src/identity/wallet/page/errorView.ts +122 -0
- package/src/identity/wallet/page/view.ts +3 -114
- package/src/mcp/manager.ts +8 -66
- package/src/mcp/managerHelpers.ts +70 -0
- package/src/models/ModelPicker.tsx +69 -889
- package/src/models/huggingface.ts +20 -137
- package/src/models/huggingfaceStorage.ts +136 -0
- package/src/models/llamacpp.ts +37 -303
- package/src/models/llamacppCommands.ts +44 -0
- package/src/models/llamacppConfig.ts +34 -0
- package/src/models/llamacppDiscovery.ts +176 -0
- package/src/models/llamacppOutput.ts +65 -0
- package/src/models/modelPickerCatalogFlow.ts +56 -0
- package/src/models/modelPickerCredentials.ts +166 -0
- package/src/models/modelPickerData.ts +41 -0
- package/src/models/modelPickerDisplay.tsx +132 -0
- package/src/models/modelPickerHfFlow.ts +192 -0
- package/src/models/modelPickerLocalRunnerFlow.ts +115 -0
- package/src/models/modelPickerTypes.ts +69 -0
- package/src/models/modelPickerUninstallFlow.ts +48 -0
- package/src/models/modelPickerViewHelpers.ts +174 -0
- package/src/providers/openai-chat.ts +5 -124
- package/src/providers/openaiChatWire.ts +124 -0
- package/src/runtime/providerTurn.ts +38 -0
- package/src/runtime/textToolParser.ts +161 -0
- package/src/runtime/toolIntent.ts +1 -1
- package/src/runtime/turn.ts +43 -499
- package/src/runtime/turnNudges.ts +223 -0
- package/src/runtime/turnTypes.ts +86 -0
- package/src/ui/terminalTitle.ts +30 -0
|
@@ -77,6 +77,10 @@ import {
|
|
|
77
77
|
syncVaultOperatorsAfterOwnerSave,
|
|
78
78
|
} from '../shared/effects/sync.js'
|
|
79
79
|
import { runOperatorWalletRebackup } from './vault.js'
|
|
80
|
+
import {
|
|
81
|
+
rebackupCompletionMessage,
|
|
82
|
+
rebackupWalletPurpose,
|
|
83
|
+
} from './completion.js'
|
|
80
84
|
|
|
81
85
|
type BackupMetadata = NonNullable<EthagentIdentity['backup']>
|
|
82
86
|
type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
|
|
@@ -418,65 +422,4 @@ export async function runRebackupStorageSubmit(
|
|
|
418
422
|
callbacks.onStep({ kind: 'rebackup-signing', identity: step.identity, registry: step.registry, pinataJwt, profileUpdates: step.profileUpdates, returnTo: step.returnTo, walletPurpose: step.walletPurpose, vaultAddress: step.vaultAddress })
|
|
419
423
|
}
|
|
420
424
|
|
|
421
|
-
|
|
422
|
-
identity: EthagentIdentity,
|
|
423
|
-
profileUpdates: ProfileUpdates | undefined,
|
|
424
|
-
): WalletPurpose {
|
|
425
|
-
const role = snapshotSaveWalletRole(identity, profileUpdates)
|
|
426
|
-
const snapshotPurpose = role === 'operator'
|
|
427
|
-
? 'update-snapshot-operator' as const
|
|
428
|
-
: role === 'owner'
|
|
429
|
-
? 'update-snapshot-owner' as const
|
|
430
|
-
: 'update-snapshot-connected' as const
|
|
431
|
-
const profilePurpose = role === 'operator'
|
|
432
|
-
? 'update-profile-operator' as const
|
|
433
|
-
: role === 'owner'
|
|
434
|
-
? 'update-profile-owner' as const
|
|
435
|
-
: 'update-profile-connected' as const
|
|
436
|
-
if (!profileUpdates) return snapshotPurpose
|
|
437
|
-
const baseState = (identity.state ?? {}) as Record<string, unknown>
|
|
438
|
-
const currentEns = typeof baseState.ensName === 'string' ? baseState.ensName.trim() : ''
|
|
439
|
-
const ensTouched = typeof profileUpdates.ensName === 'string'
|
|
440
|
-
const profileFieldsTouched = profileUpdates.name !== undefined
|
|
441
|
-
|| profileUpdates.description !== undefined
|
|
442
|
-
|| profileUpdates.imagePath !== undefined
|
|
443
|
-
const operatorFieldsTouched = profileUpdates.ownerAddress !== undefined
|
|
444
|
-
|| profileUpdates.approvedOperatorWallets !== undefined
|
|
445
|
-
|| profileUpdates.activeOperatorAddress !== undefined
|
|
446
|
-
|| profileUpdates.restoreAccessEpoch !== undefined
|
|
447
|
-
if (operatorFieldsTouched && !ensTouched && !profileFieldsTouched) return 'update-operators'
|
|
448
|
-
if (ensTouched && !profileFieldsTouched) {
|
|
449
|
-
const next = (profileUpdates.ensName ?? '').trim()
|
|
450
|
-
if (!next && currentEns) return 'clear-ens'
|
|
451
|
-
return 'update-ens'
|
|
452
|
-
}
|
|
453
|
-
if (profileFieldsTouched) return profilePurpose
|
|
454
|
-
return snapshotPurpose
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
export function rebackupCompletionMessage(
|
|
458
|
-
profileUpdates: ProfileUpdates | undefined,
|
|
459
|
-
identity: EthagentIdentity,
|
|
460
|
-
ensOk?: boolean,
|
|
461
|
-
): string {
|
|
462
|
-
if (!profileUpdates) return 'Backup Saved'
|
|
463
|
-
const baseState = (identity.state ?? {}) as Record<string, unknown>
|
|
464
|
-
const currentEns = typeof baseState.ensName === 'string' ? baseState.ensName.trim() : ''
|
|
465
|
-
const ensTouched = typeof profileUpdates.ensName === 'string'
|
|
466
|
-
const profileFieldsTouched = profileUpdates.name !== undefined
|
|
467
|
-
|| profileUpdates.description !== undefined
|
|
468
|
-
|| profileUpdates.imagePath !== undefined
|
|
469
|
-
const operatorFieldsTouched = profileUpdates.ownerAddress !== undefined
|
|
470
|
-
|| profileUpdates.approvedOperatorWallets !== undefined
|
|
471
|
-
|| profileUpdates.activeOperatorAddress !== undefined
|
|
472
|
-
|| profileUpdates.restoreAccessEpoch !== undefined
|
|
473
|
-
if (operatorFieldsTouched && !ensTouched && !profileFieldsTouched) return 'Operator Wallets Updated'
|
|
474
|
-
if (ensTouched && !profileFieldsTouched) {
|
|
475
|
-
const next = (profileUpdates.ensName ?? '').trim()
|
|
476
|
-
if (!next && currentEns) return 'ENS Unlinked'
|
|
477
|
-
if (next) return ensOk === false ? 'ENS Issue' : 'ENS Linked'
|
|
478
|
-
return 'ENS Updated'
|
|
479
|
-
}
|
|
480
|
-
if (profileFieldsTouched) return 'Profile Updated'
|
|
481
|
-
return 'Backup Saved'
|
|
482
|
-
}
|
|
425
|
+
export { rebackupCompletionMessage } from './completion.js'
|
|
@@ -6,21 +6,15 @@ import {
|
|
|
6
6
|
type WalletChallengePurpose,
|
|
7
7
|
} from '../../continuity/envelope.js'
|
|
8
8
|
import {
|
|
9
|
-
prepareSyncedSkillsTree,
|
|
10
9
|
prepareSyncedPublicSkillsJson,
|
|
11
|
-
readContinuityFiles,
|
|
12
10
|
writePublicSkillsFile,
|
|
13
11
|
} from '../../continuity/storage.js'
|
|
14
12
|
import {
|
|
15
|
-
appendPublicSkillEntries,
|
|
16
13
|
createAgentCard,
|
|
17
14
|
defaultPublicSkillsProfile,
|
|
18
15
|
serializeAgentCard,
|
|
19
16
|
} from '../../continuity/publicSkills.js'
|
|
20
|
-
import {
|
|
21
|
-
derivePublicSkillEntries,
|
|
22
|
-
syncPublicSkillsManifest,
|
|
23
|
-
} from '../../continuity/skills/publicSkillsSync.js'
|
|
17
|
+
import { syncPublicSkillsManifest } from '../../continuity/skills/publicSkillsSync.js'
|
|
24
18
|
import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
|
|
25
19
|
import { addToIpfs, DEFAULT_IPFS_API_URL, isPinataUploadUrl } from '../../storage/ipfs.js'
|
|
26
20
|
import {
|
|
@@ -30,10 +24,7 @@ import {
|
|
|
30
24
|
withEthagentPointers,
|
|
31
25
|
type Erc8004RegistryConfig,
|
|
32
26
|
} from '../../registry/erc8004.js'
|
|
33
|
-
import {
|
|
34
|
-
VAULT_ABI,
|
|
35
|
-
encodeRotateAgentURI,
|
|
36
|
-
} from '../../registry/vault.js'
|
|
27
|
+
import { encodeRotateAgentURI } from '../../registry/vault.js'
|
|
37
28
|
import { resolveValidatedPinataJwt, savePinataJwt } from '../../storage/pinataJwt.js'
|
|
38
29
|
import {
|
|
39
30
|
openBrowserWalletSession,
|
|
@@ -67,8 +58,11 @@ import {
|
|
|
67
58
|
walletRestoreAccessContext,
|
|
68
59
|
} from '../continuity/snapshot.js'
|
|
69
60
|
import { rebackupCompletionMessage } from '../continuity/effects.js'
|
|
61
|
+
import {
|
|
62
|
+
assertVaultSignerCanRotateAgentUri,
|
|
63
|
+
prepareOperatorProfileArtifacts,
|
|
64
|
+
} from './operatorSave.js'
|
|
70
65
|
|
|
71
|
-
type BackupMetadata = NonNullable<EthagentIdentity['backup']>
|
|
72
66
|
type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
|
|
73
67
|
|
|
74
68
|
type PublicProfilePreparedTransaction = {
|
|
@@ -369,164 +363,6 @@ async function runOperatorWalletVaultPublicProfileSave(args: {
|
|
|
369
363
|
}
|
|
370
364
|
}
|
|
371
365
|
|
|
372
|
-
type OperatorProfileArtifacts = {
|
|
373
|
-
nextIdentity: EthagentIdentity
|
|
374
|
-
publicSkillsJson: string
|
|
375
|
-
agentUri: string
|
|
376
|
-
metadataCid: string
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
async function prepareOperatorProfileArtifacts(args: {
|
|
380
|
-
step: Extract<Step, { kind: 'public-profile-signing' }>
|
|
381
|
-
wallet: BrowserWalletSignature
|
|
382
|
-
snapshotOwner: Address
|
|
383
|
-
walletAccess: WalletAccessContext
|
|
384
|
-
challengePurpose: WalletChallengePurpose
|
|
385
|
-
}): Promise<OperatorProfileArtifacts> {
|
|
386
|
-
const { step, wallet, snapshotOwner, walletAccess, challengePurpose } = args
|
|
387
|
-
const {
|
|
388
|
-
state,
|
|
389
|
-
nextName,
|
|
390
|
-
nextDescription,
|
|
391
|
-
nextEnsName,
|
|
392
|
-
uploadedImageUri,
|
|
393
|
-
} = await prepareProfileStateForSave({
|
|
394
|
-
identity: step.identity,
|
|
395
|
-
registry: step.registry,
|
|
396
|
-
profileUpdates: step.profileUpdates,
|
|
397
|
-
pinataJwt: step.pinataJwt,
|
|
398
|
-
ownerAddress: snapshotOwner,
|
|
399
|
-
walletAccount: getAddress(wallet.account),
|
|
400
|
-
includeLastBackedUpAt: true,
|
|
401
|
-
})
|
|
402
|
-
const nextIdentityForFiles: EthagentIdentity = { ...step.identity, state }
|
|
403
|
-
|
|
404
|
-
const publicSkillsJson = await syncPublicSkillsManifest(nextIdentityForFiles)
|
|
405
|
-
const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
|
|
406
|
-
assertVerifiedPin(publicSkillsPin)
|
|
407
|
-
const publicSkillEntries = await derivePublicSkillEntries(nextIdentityForFiles)
|
|
408
|
-
const augmentedPublicProfile = appendPublicSkillEntries(
|
|
409
|
-
defaultPublicSkillsProfile(nextIdentityForFiles),
|
|
410
|
-
publicSkillEntries,
|
|
411
|
-
)
|
|
412
|
-
const agentCardPin = await addToIpfs(
|
|
413
|
-
DEFAULT_IPFS_API_URL,
|
|
414
|
-
serializeAgentCard(createAgentCard(augmentedPublicProfile)),
|
|
415
|
-
fetch,
|
|
416
|
-
{ pinataJwt: step.pinataJwt },
|
|
417
|
-
)
|
|
418
|
-
assertVerifiedPin(agentCardPin)
|
|
419
|
-
|
|
420
|
-
const continuityFiles = await readContinuityFiles(nextIdentityForFiles)
|
|
421
|
-
const skillsTree = await prepareSyncedSkillsTree(nextIdentityForFiles)
|
|
422
|
-
const envelope = createContinuityEnvelopeForSave({
|
|
423
|
-
identity: nextIdentityForFiles,
|
|
424
|
-
registry: step.registry,
|
|
425
|
-
ownerAddress: snapshotOwner,
|
|
426
|
-
signerAddress: wallet.account,
|
|
427
|
-
walletSignature: wallet.signature,
|
|
428
|
-
state,
|
|
429
|
-
files: continuityFiles,
|
|
430
|
-
skills: skillsTree,
|
|
431
|
-
walletAccess,
|
|
432
|
-
challengePurpose,
|
|
433
|
-
})
|
|
434
|
-
const statePin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeContinuitySnapshotEnvelope(envelope), fetch, { pinataJwt: step.pinataJwt })
|
|
435
|
-
assertVerifiedPin(statePin)
|
|
436
|
-
|
|
437
|
-
const publicSkills: PublicSkillsMetadata = {
|
|
438
|
-
cid: publicSkillsPin.cid,
|
|
439
|
-
agentCardCid: agentCardPin.cid,
|
|
440
|
-
updatedAt: envelope.createdAt,
|
|
441
|
-
status: 'pinned',
|
|
442
|
-
}
|
|
443
|
-
const backup: BackupMetadata = {
|
|
444
|
-
cid: statePin.cid,
|
|
445
|
-
createdAt: envelope.createdAt,
|
|
446
|
-
envelopeVersion: envelope.envelopeVersion,
|
|
447
|
-
ipfsApiUrl: DEFAULT_IPFS_API_URL,
|
|
448
|
-
status: 'pinned',
|
|
449
|
-
ownerAddress: snapshotOwner,
|
|
450
|
-
chainId: step.registry.chainId,
|
|
451
|
-
rpcUrl: step.registry.rpcUrl,
|
|
452
|
-
identityRegistryAddress: step.registry.identityRegistryAddress,
|
|
453
|
-
agentId: step.identity.agentId!,
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
const registration = withEthagentPointers({
|
|
457
|
-
type: 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1',
|
|
458
|
-
name: nextName ?? deriveAgentName(step.identity),
|
|
459
|
-
...(nextDescription ? { description: nextDescription } : {}),
|
|
460
|
-
...(uploadedImageUri ? { image: uploadedImageUri } : {}),
|
|
461
|
-
}, {
|
|
462
|
-
backup: { cid: statePin.cid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt },
|
|
463
|
-
publicDiscovery: { skillsCid: publicSkills.cid, agentCardCid: publicSkills.agentCardCid, updatedAt: publicSkills.updatedAt },
|
|
464
|
-
registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress, agentId: step.identity.agentId },
|
|
465
|
-
ensName: nextEnsName,
|
|
466
|
-
operators: operatorsPointerFromState(state, nextEnsName),
|
|
467
|
-
ownerAddress: snapshotOwner,
|
|
468
|
-
})
|
|
469
|
-
const metadataPin = await addToIpfs(DEFAULT_IPFS_API_URL, JSON.stringify(registration, null, 2), fetch, { pinataJwt: step.pinataJwt })
|
|
470
|
-
assertVerifiedPin(metadataPin)
|
|
471
|
-
const metadataCid = metadataPin.cid
|
|
472
|
-
const agentUri = `ipfs://${metadataCid}`
|
|
473
|
-
|
|
474
|
-
const nextIdentity: EthagentIdentity = {
|
|
475
|
-
...step.identity,
|
|
476
|
-
state,
|
|
477
|
-
backup: { ...backup, metadataCid, agentUri },
|
|
478
|
-
publicSkills,
|
|
479
|
-
agentUri,
|
|
480
|
-
metadataCid,
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
return {
|
|
484
|
-
nextIdentity,
|
|
485
|
-
publicSkillsJson,
|
|
486
|
-
agentUri,
|
|
487
|
-
metadataCid,
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
async function assertVaultSignerCanRotateAgentUri(args: {
|
|
492
|
-
registry: Erc8004RegistryConfig
|
|
493
|
-
vaultAddress: Address
|
|
494
|
-
agentId: bigint
|
|
495
|
-
signer: Address
|
|
496
|
-
}): Promise<void> {
|
|
497
|
-
const client = createErc8004PublicClient(args.registry)
|
|
498
|
-
const registryAddress = getAddress(args.registry.identityRegistryAddress)
|
|
499
|
-
const vaultAddress = getAddress(args.vaultAddress)
|
|
500
|
-
const signer = getAddress(args.signer)
|
|
501
|
-
let vaultOwner: Address
|
|
502
|
-
try {
|
|
503
|
-
vaultOwner = getAddress(await client.readContract({
|
|
504
|
-
address: vaultAddress,
|
|
505
|
-
abi: VAULT_ABI,
|
|
506
|
-
functionName: 'agentOwner',
|
|
507
|
-
args: [registryAddress, args.agentId],
|
|
508
|
-
}) as Address)
|
|
509
|
-
} catch (err: unknown) {
|
|
510
|
-
throw new Error(`Could not verify Vault custody for agent #${args.agentId.toString()}: ${err instanceof Error ? err.message : String(err)}`)
|
|
511
|
-
}
|
|
512
|
-
if (vaultOwner === '0x0000000000000000000000000000000000000000') {
|
|
513
|
-
throw new Error(`Vault ${vaultAddress} does not currently hold agent token #${args.agentId.toString()}. Connect the owner wallet and run "Fix Records" or return the token to the vault before retrying.`)
|
|
514
|
-
}
|
|
515
|
-
if (vaultOwner.toLowerCase() === signer.toLowerCase()) return
|
|
516
|
-
|
|
517
|
-
const isOperator = await client.readContract({
|
|
518
|
-
address: vaultAddress,
|
|
519
|
-
abi: VAULT_ABI,
|
|
520
|
-
functionName: 'metadataOperators',
|
|
521
|
-
args: [registryAddress, args.agentId, signer],
|
|
522
|
-
}) as boolean
|
|
523
|
-
if (isOperator) return
|
|
524
|
-
|
|
525
|
-
throw new Error(
|
|
526
|
-
`Operator wallet ${signer} is not yet authorized on the Vault to rotate this agent's URI. Connect the owner wallet and run "Fix Records" or re-add this operator to grant the permission.`,
|
|
527
|
-
)
|
|
528
|
-
}
|
|
529
|
-
|
|
530
366
|
export async function runPublicProfileStorageSubmit(
|
|
531
367
|
input: string,
|
|
532
368
|
step: Extract<Step, { kind: 'public-profile-storage' }>,
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { getAddress, type Address } from 'viem'
|
|
2
|
+
import type { EthagentIdentity } from '../../../storage/config.js'
|
|
3
|
+
import {
|
|
4
|
+
createWalletRestoreAccessChallenge,
|
|
5
|
+
serializeContinuitySnapshotEnvelope,
|
|
6
|
+
type WalletChallengePurpose,
|
|
7
|
+
} from '../../continuity/envelope.js'
|
|
8
|
+
import {
|
|
9
|
+
prepareSyncedSkillsTree,
|
|
10
|
+
readContinuityFiles,
|
|
11
|
+
} from '../../continuity/storage.js'
|
|
12
|
+
import {
|
|
13
|
+
appendPublicSkillEntries,
|
|
14
|
+
createAgentCard,
|
|
15
|
+
defaultPublicSkillsProfile,
|
|
16
|
+
serializeAgentCard,
|
|
17
|
+
} from '../../continuity/publicSkills.js'
|
|
18
|
+
import {
|
|
19
|
+
derivePublicSkillEntries,
|
|
20
|
+
syncPublicSkillsManifest,
|
|
21
|
+
} from '../../continuity/skills/publicSkillsSync.js'
|
|
22
|
+
import { addToIpfs, DEFAULT_IPFS_API_URL } from '../../storage/ipfs.js'
|
|
23
|
+
import {
|
|
24
|
+
createErc8004PublicClient,
|
|
25
|
+
withEthagentPointers,
|
|
26
|
+
type Erc8004RegistryConfig,
|
|
27
|
+
} from '../../registry/erc8004.js'
|
|
28
|
+
import { VAULT_ABI } from '../../registry/vault.js'
|
|
29
|
+
import type { BrowserWalletSignature } from '../../wallet/browserWallet.js'
|
|
30
|
+
import type { Step } from '../identityHubReducer.js'
|
|
31
|
+
import {
|
|
32
|
+
assertVerifiedPin,
|
|
33
|
+
deriveAgentName,
|
|
34
|
+
prepareProfileStateForSave,
|
|
35
|
+
} from '../shared/effects/profilePrep.js'
|
|
36
|
+
import {
|
|
37
|
+
createContinuityEnvelopeForSave,
|
|
38
|
+
operatorsPointerFromState,
|
|
39
|
+
walletRestoreAccessContext,
|
|
40
|
+
} from '../continuity/snapshot.js'
|
|
41
|
+
|
|
42
|
+
type BackupMetadata = NonNullable<EthagentIdentity['backup']>
|
|
43
|
+
type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
|
|
44
|
+
type WalletAccessContext = NonNullable<ReturnType<typeof walletRestoreAccessContext>>
|
|
45
|
+
|
|
46
|
+
export type OperatorProfileArtifacts = {
|
|
47
|
+
nextIdentity: EthagentIdentity
|
|
48
|
+
publicSkillsJson: string
|
|
49
|
+
agentUri: string
|
|
50
|
+
metadataCid: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function prepareOperatorProfileArtifacts(args: {
|
|
54
|
+
step: Extract<Step, { kind: 'public-profile-signing' }>
|
|
55
|
+
wallet: BrowserWalletSignature
|
|
56
|
+
snapshotOwner: Address
|
|
57
|
+
walletAccess: WalletAccessContext
|
|
58
|
+
challengePurpose: WalletChallengePurpose
|
|
59
|
+
}): Promise<OperatorProfileArtifacts> {
|
|
60
|
+
const { step, wallet, snapshotOwner, walletAccess, challengePurpose } = args
|
|
61
|
+
const {
|
|
62
|
+
state,
|
|
63
|
+
nextName,
|
|
64
|
+
nextDescription,
|
|
65
|
+
nextEnsName,
|
|
66
|
+
uploadedImageUri,
|
|
67
|
+
} = await prepareProfileStateForSave({
|
|
68
|
+
identity: step.identity,
|
|
69
|
+
registry: step.registry,
|
|
70
|
+
profileUpdates: step.profileUpdates,
|
|
71
|
+
pinataJwt: step.pinataJwt,
|
|
72
|
+
ownerAddress: snapshotOwner,
|
|
73
|
+
walletAccount: getAddress(wallet.account),
|
|
74
|
+
includeLastBackedUpAt: true,
|
|
75
|
+
})
|
|
76
|
+
const nextIdentityForFiles: EthagentIdentity = { ...step.identity, state }
|
|
77
|
+
|
|
78
|
+
const publicSkillsJson = await syncPublicSkillsManifest(nextIdentityForFiles)
|
|
79
|
+
const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
|
|
80
|
+
assertVerifiedPin(publicSkillsPin)
|
|
81
|
+
const publicSkillEntries = await derivePublicSkillEntries(nextIdentityForFiles)
|
|
82
|
+
const augmentedPublicProfile = appendPublicSkillEntries(
|
|
83
|
+
defaultPublicSkillsProfile(nextIdentityForFiles),
|
|
84
|
+
publicSkillEntries,
|
|
85
|
+
)
|
|
86
|
+
const agentCardPin = await addToIpfs(
|
|
87
|
+
DEFAULT_IPFS_API_URL,
|
|
88
|
+
serializeAgentCard(createAgentCard(augmentedPublicProfile)),
|
|
89
|
+
fetch,
|
|
90
|
+
{ pinataJwt: step.pinataJwt },
|
|
91
|
+
)
|
|
92
|
+
assertVerifiedPin(agentCardPin)
|
|
93
|
+
|
|
94
|
+
const continuityFiles = await readContinuityFiles(nextIdentityForFiles)
|
|
95
|
+
const skillsTree = await prepareSyncedSkillsTree(nextIdentityForFiles)
|
|
96
|
+
const envelope = createContinuityEnvelopeForSave({
|
|
97
|
+
identity: nextIdentityForFiles,
|
|
98
|
+
registry: step.registry,
|
|
99
|
+
ownerAddress: snapshotOwner,
|
|
100
|
+
signerAddress: wallet.account,
|
|
101
|
+
walletSignature: wallet.signature,
|
|
102
|
+
state,
|
|
103
|
+
files: continuityFiles,
|
|
104
|
+
skills: skillsTree,
|
|
105
|
+
walletAccess,
|
|
106
|
+
challengePurpose,
|
|
107
|
+
})
|
|
108
|
+
const statePin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeContinuitySnapshotEnvelope(envelope), fetch, { pinataJwt: step.pinataJwt })
|
|
109
|
+
assertVerifiedPin(statePin)
|
|
110
|
+
|
|
111
|
+
const publicSkills: PublicSkillsMetadata = {
|
|
112
|
+
cid: publicSkillsPin.cid,
|
|
113
|
+
agentCardCid: agentCardPin.cid,
|
|
114
|
+
updatedAt: envelope.createdAt,
|
|
115
|
+
status: 'pinned',
|
|
116
|
+
}
|
|
117
|
+
const backup: BackupMetadata = {
|
|
118
|
+
cid: statePin.cid,
|
|
119
|
+
createdAt: envelope.createdAt,
|
|
120
|
+
envelopeVersion: envelope.envelopeVersion,
|
|
121
|
+
ipfsApiUrl: DEFAULT_IPFS_API_URL,
|
|
122
|
+
status: 'pinned',
|
|
123
|
+
ownerAddress: snapshotOwner,
|
|
124
|
+
chainId: step.registry.chainId,
|
|
125
|
+
rpcUrl: step.registry.rpcUrl,
|
|
126
|
+
identityRegistryAddress: step.registry.identityRegistryAddress,
|
|
127
|
+
agentId: step.identity.agentId!,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const registration = withEthagentPointers({
|
|
131
|
+
type: 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1',
|
|
132
|
+
name: nextName ?? deriveAgentName(step.identity),
|
|
133
|
+
...(nextDescription ? { description: nextDescription } : {}),
|
|
134
|
+
...(uploadedImageUri ? { image: uploadedImageUri } : {}),
|
|
135
|
+
}, {
|
|
136
|
+
backup: { cid: statePin.cid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt },
|
|
137
|
+
publicDiscovery: { skillsCid: publicSkills.cid, agentCardCid: publicSkills.agentCardCid, updatedAt: publicSkills.updatedAt },
|
|
138
|
+
registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress, agentId: step.identity.agentId },
|
|
139
|
+
ensName: nextEnsName,
|
|
140
|
+
operators: operatorsPointerFromState(state, nextEnsName),
|
|
141
|
+
ownerAddress: snapshotOwner,
|
|
142
|
+
})
|
|
143
|
+
const metadataPin = await addToIpfs(DEFAULT_IPFS_API_URL, JSON.stringify(registration, null, 2), fetch, { pinataJwt: step.pinataJwt })
|
|
144
|
+
assertVerifiedPin(metadataPin)
|
|
145
|
+
const metadataCid = metadataPin.cid
|
|
146
|
+
const agentUri = `ipfs://${metadataCid}`
|
|
147
|
+
|
|
148
|
+
const nextIdentity: EthagentIdentity = {
|
|
149
|
+
...step.identity,
|
|
150
|
+
state,
|
|
151
|
+
backup: { ...backup, metadataCid, agentUri },
|
|
152
|
+
publicSkills,
|
|
153
|
+
agentUri,
|
|
154
|
+
metadataCid,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
nextIdentity,
|
|
159
|
+
publicSkillsJson,
|
|
160
|
+
agentUri,
|
|
161
|
+
metadataCid,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function assertVaultSignerCanRotateAgentUri(args: {
|
|
166
|
+
registry: Erc8004RegistryConfig
|
|
167
|
+
vaultAddress: Address
|
|
168
|
+
agentId: bigint
|
|
169
|
+
signer: Address
|
|
170
|
+
}): Promise<void> {
|
|
171
|
+
const client = createErc8004PublicClient(args.registry)
|
|
172
|
+
const registryAddress = getAddress(args.registry.identityRegistryAddress)
|
|
173
|
+
const vaultAddress = getAddress(args.vaultAddress)
|
|
174
|
+
const signer = getAddress(args.signer)
|
|
175
|
+
let vaultOwner: Address
|
|
176
|
+
try {
|
|
177
|
+
vaultOwner = getAddress(await client.readContract({
|
|
178
|
+
address: vaultAddress,
|
|
179
|
+
abi: VAULT_ABI,
|
|
180
|
+
functionName: 'agentOwner',
|
|
181
|
+
args: [registryAddress, args.agentId],
|
|
182
|
+
}) as Address)
|
|
183
|
+
} catch (err: unknown) {
|
|
184
|
+
throw new Error(`Could not verify Vault custody for agent #${args.agentId.toString()}: ${err instanceof Error ? err.message : String(err)}`)
|
|
185
|
+
}
|
|
186
|
+
if (vaultOwner === '0x0000000000000000000000000000000000000000') {
|
|
187
|
+
throw new Error(`Vault ${vaultAddress} does not currently hold agent token #${args.agentId.toString()}. Connect the owner wallet and run "Fix Records" or return the token to the vault before retrying.`)
|
|
188
|
+
}
|
|
189
|
+
if (vaultOwner.toLowerCase() === signer.toLowerCase()) return
|
|
190
|
+
|
|
191
|
+
const isOperator = await client.readContract({
|
|
192
|
+
address: vaultAddress,
|
|
193
|
+
abi: VAULT_ABI,
|
|
194
|
+
functionName: 'metadataOperators',
|
|
195
|
+
args: [registryAddress, args.agentId, signer],
|
|
196
|
+
}) as boolean
|
|
197
|
+
if (isOperator) return
|
|
198
|
+
|
|
199
|
+
throw new Error(
|
|
200
|
+
`Operator wallet ${signer} is not yet authorized on the Vault to rotate this agent's URI. Connect the owner wallet and run "Fix Records" or re-add this operator to grant the permission.`,
|
|
201
|
+
)
|
|
202
|
+
}
|
|
@@ -181,16 +181,15 @@ function serializeOperatorsPointer(pointer: EthagentOperatorsPointer): Record<st
|
|
|
181
181
|
export function withEthagentBackupPointer(
|
|
182
182
|
registration: Record<string, unknown> | null,
|
|
183
183
|
backup: EthagentBackupPointer,
|
|
184
|
-
publicDiscovery
|
|
185
|
-
registrationPointer
|
|
186
|
-
ownerAddress
|
|
184
|
+
publicDiscovery: EthagentPublicDiscoveryPointer | undefined,
|
|
185
|
+
registrationPointer: EthagentRegistrationPointer | undefined,
|
|
186
|
+
ownerAddress: Address,
|
|
187
187
|
): Record<string, unknown> {
|
|
188
|
-
const inferredOwnerAddress = ownerAddress ?? backup.agentAddress
|
|
189
188
|
return withEthagentPointers(registration, {
|
|
190
189
|
backup,
|
|
191
190
|
publicDiscovery,
|
|
192
191
|
registration: registrationPointer,
|
|
193
|
-
|
|
192
|
+
ownerAddress,
|
|
194
193
|
})
|
|
195
194
|
}
|
|
196
195
|
|
|
@@ -202,29 +201,26 @@ export function withEthagentPointers(
|
|
|
202
201
|
registration?: EthagentRegistrationPointer
|
|
203
202
|
ensName?: string
|
|
204
203
|
operators?: EthagentOperatorsPointer
|
|
205
|
-
ownerAddress
|
|
204
|
+
ownerAddress: Address
|
|
206
205
|
},
|
|
207
206
|
): Record<string, unknown> {
|
|
208
207
|
const next: Record<string, unknown> = registration ? { ...registration } : {}
|
|
209
208
|
const prior = objectField(next, 'x-ethagent') ?? {}
|
|
210
209
|
const { backup, publicDiscovery, registration: registrationPointer, operators } = pointers
|
|
211
210
|
const updatedAt = publicDiscovery?.updatedAt ?? backup?.createdAt
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
: undefined
|
|
211
|
+
if (!pointers.ownerAddress) {
|
|
212
|
+
throw new Error('withEthagentPointers requires ownerAddress')
|
|
213
|
+
}
|
|
214
|
+
const ownerAddress = getAddress(pointers.ownerAddress)
|
|
217
215
|
const priorX402 = objectField(prior, 'x402') ?? {}
|
|
218
216
|
const ext: Record<string, unknown> = {
|
|
219
217
|
...prior,
|
|
220
218
|
version: 1,
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
},
|
|
227
|
-
} : {}),
|
|
219
|
+
agentAddress: ownerAddress,
|
|
220
|
+
x402: {
|
|
221
|
+
...priorX402,
|
|
222
|
+
walletAddress: ownerAddress,
|
|
223
|
+
},
|
|
228
224
|
...(backup ? {
|
|
229
225
|
backup: {
|
|
230
226
|
cid: backup.cid,
|
|
@@ -252,8 +248,11 @@ export function withEthagentPointers(
|
|
|
252
248
|
delete ext.transfer
|
|
253
249
|
delete ext.handoff
|
|
254
250
|
next['x-ethagent'] = ext
|
|
255
|
-
|
|
256
|
-
|
|
251
|
+
const agentWalletService = registrationPointer
|
|
252
|
+
? { name: 'agentWallet' as const, endpoint: `eip155:${registrationPointer.chainId}:${ownerAddress}` }
|
|
253
|
+
: undefined
|
|
254
|
+
if (publicDiscovery || agentWalletService) {
|
|
255
|
+
next.services = withEthagentServices(next.services, publicDiscovery, pointers.ensName, agentWalletService)
|
|
257
256
|
}
|
|
258
257
|
if (registrationPointer && registrationPointer.agentId !== undefined) {
|
|
259
258
|
next.registrations = withRegistrationsArray(next.registrations, registrationPointer)
|
|
@@ -272,10 +271,18 @@ function serializeTransferSnapshotMetadata(metadata: TransferSnapshotMetadata):
|
|
|
272
271
|
}
|
|
273
272
|
}
|
|
274
273
|
|
|
275
|
-
function
|
|
274
|
+
function withEthagentServices(
|
|
275
|
+
input: unknown,
|
|
276
|
+
publicDiscovery: EthagentPublicDiscoveryPointer | undefined,
|
|
277
|
+
ensName: string | undefined,
|
|
278
|
+
agentWallet: { name: 'agentWallet'; endpoint: string } | undefined,
|
|
279
|
+
): unknown[] {
|
|
276
280
|
const prior = Array.isArray(input) ? input.filter(item => item && typeof item === 'object') : []
|
|
277
281
|
const services = prior.filter(item => !isEthagentManagedService(item)) as unknown[]
|
|
278
|
-
if (
|
|
282
|
+
if (agentWallet) {
|
|
283
|
+
pushUniqueService(services, agentWallet)
|
|
284
|
+
}
|
|
285
|
+
if (publicDiscovery?.agentCardCid) {
|
|
279
286
|
const endpoint = `ipfs://${publicDiscovery.agentCardCid}`
|
|
280
287
|
pushUniqueService(services, {
|
|
281
288
|
type: 'a2a',
|
|
@@ -284,7 +291,7 @@ function withPublicDiscoveryServices(input: unknown, publicDiscovery: EthagentPu
|
|
|
284
291
|
url: endpoint,
|
|
285
292
|
})
|
|
286
293
|
}
|
|
287
|
-
if (publicDiscovery
|
|
294
|
+
if (publicDiscovery?.skillsCid) {
|
|
288
295
|
const endpoint = `ipfs://${publicDiscovery.skillsCid}`
|
|
289
296
|
pushUniqueService(services, {
|
|
290
297
|
type: 'A2A-skills',
|
|
@@ -313,6 +320,7 @@ function isEthagentManagedService(item: unknown): boolean {
|
|
|
313
320
|
const obj = item as Record<string, unknown>
|
|
314
321
|
const type = obj.type
|
|
315
322
|
const name = obj.name
|
|
323
|
+
if (name === 'agentWallet') return true
|
|
316
324
|
if (name === 'ENS') return true
|
|
317
325
|
if (type === 'a2a' && (name === undefined || name === 'agent-card')) return true
|
|
318
326
|
return (type === 'A2A-skills' || type === 'ipfs') && name === 'public-skills'
|
|
@@ -1,26 +1,7 @@
|
|
|
1
|
-
import { readFileSync, statSync } from 'node:fs'
|
|
2
|
-
import { dirname, join } from 'node:path'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
4
1
|
import { transformSync } from 'esbuild'
|
|
5
2
|
import { normalizeWalletPayloadPurpose } from '../walletPurposeCompat.js'
|
|
3
|
+
import { loadWalletPageSource } from './walletPageSource.js'
|
|
6
4
|
|
|
7
|
-
const WALLET_PAGE_FILE = join(dirname(fileURLToPath(import.meta.url)), '..', 'page.tsx')
|
|
8
|
-
const WALLET_PAGE_MODULE_FILES = [
|
|
9
|
-
join('page', 'types.ts'),
|
|
10
|
-
join('page', 'html.ts'),
|
|
11
|
-
join('page', 'constants.ts'),
|
|
12
|
-
join('page', 'styles', 'base.ts'),
|
|
13
|
-
join('page', 'styles', 'components.ts'),
|
|
14
|
-
join('page', 'styles', 'responsive.ts'),
|
|
15
|
-
join('page', 'styles', 'index.ts'),
|
|
16
|
-
join('page', 'markup.ts'),
|
|
17
|
-
join('page', 'grainient.ts'),
|
|
18
|
-
join('page', 'state.ts'),
|
|
19
|
-
join('page', 'copy.ts'),
|
|
20
|
-
join('page', 'walletProvider.ts'),
|
|
21
|
-
join('page', 'view.ts'),
|
|
22
|
-
join('page', 'controller.ts'),
|
|
23
|
-
] as const
|
|
24
5
|
const WALLET_HTML = loadWalletHtml()
|
|
25
6
|
|
|
26
7
|
export function walletPage(title: string, sessionToken: string, payload: Record<string, unknown>): string {
|
|
@@ -43,43 +24,6 @@ function loadWalletHtml(): string {
|
|
|
43
24
|
return wrapInWalletShell(compiled)
|
|
44
25
|
}
|
|
45
26
|
|
|
46
|
-
function loadWalletPageSource(): string {
|
|
47
|
-
const pageFile = locateWalletPageFile()
|
|
48
|
-
const pageDir = dirname(pageFile)
|
|
49
|
-
const files = [
|
|
50
|
-
...WALLET_PAGE_MODULE_FILES.map(file => join(pageDir, file)),
|
|
51
|
-
pageFile,
|
|
52
|
-
]
|
|
53
|
-
return stripWalletModuleSyntax(files.map(file => readFileSync(file, 'utf8')).join('\n'))
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function stripWalletModuleSyntax(source: string): string {
|
|
57
|
-
const out: string[] = []
|
|
58
|
-
let skippingImport = false
|
|
59
|
-
for (const line of source.split(/\r?\n/)) {
|
|
60
|
-
const trimmed = line.trim()
|
|
61
|
-
if (skippingImport) {
|
|
62
|
-
if (/\bfrom\s+['"][^'"]+['"]/.test(trimmed) || trimmed.endsWith(';')) skippingImport = false
|
|
63
|
-
continue
|
|
64
|
-
}
|
|
65
|
-
if (trimmed.startsWith('import ')) {
|
|
66
|
-
if (!/\bfrom\s+['"][^'"]+['"]/.test(trimmed) && !trimmed.endsWith(';')) skippingImport = true
|
|
67
|
-
continue
|
|
68
|
-
}
|
|
69
|
-
out.push(line.replace(/^export\s+(?=(async\s+function|const|let|function|interface|type|class)\b)/, ''))
|
|
70
|
-
}
|
|
71
|
-
return out.join('\n')
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function locateWalletPageFile(): string {
|
|
75
|
-
try {
|
|
76
|
-
statSync(WALLET_PAGE_FILE)
|
|
77
|
-
return WALLET_PAGE_FILE
|
|
78
|
-
} catch {
|
|
79
|
-
return join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..', '..', 'src', 'identity', 'wallet', 'page.tsx')
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
27
|
function wrapInWalletShell(compiledJs: string): string {
|
|
84
28
|
const safeJs = compiledJs.replaceAll('</', '<\\/')
|
|
85
29
|
return `<!doctype html>
|