ethagent 3.0.2 → 3.1.1
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/ChatBottomPane.tsx +0 -3
- package/src/chat/ChatScreen.tsx +15 -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 -146
- 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/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/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/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
|
@@ -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
|
+
}
|
|
@@ -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>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
const WALLET_PAGE_ENTRY_FILE = 'page.tsx'
|
|
6
|
+
const WALLET_PAGE_MODULE_FILES = [
|
|
7
|
+
join('page', 'types.ts'),
|
|
8
|
+
join('page', 'html.ts'),
|
|
9
|
+
join('page', 'constants.ts'),
|
|
10
|
+
join('page', 'styles', 'base.ts'),
|
|
11
|
+
join('page', 'styles', 'components.ts'),
|
|
12
|
+
join('page', 'styles', 'responsive.ts'),
|
|
13
|
+
join('page', 'styles', 'index.ts'),
|
|
14
|
+
join('page', 'markup.ts'),
|
|
15
|
+
join('page', 'grainient.ts'),
|
|
16
|
+
join('page', 'state.ts'),
|
|
17
|
+
join('page', 'copy.ts'),
|
|
18
|
+
join('page', 'errorView.ts'),
|
|
19
|
+
join('page', 'walletProvider.ts'),
|
|
20
|
+
join('page', 'view.ts'),
|
|
21
|
+
join('page', 'controller.ts'),
|
|
22
|
+
] as const
|
|
23
|
+
|
|
24
|
+
export function walletPageSourceFiles(fromUrl = import.meta.url): string[] {
|
|
25
|
+
const sourceRoot = locateWalletPageSourceRoot(fromUrl)
|
|
26
|
+
return [
|
|
27
|
+
...WALLET_PAGE_MODULE_FILES.map(file => join(sourceRoot, file)),
|
|
28
|
+
join(sourceRoot, WALLET_PAGE_ENTRY_FILE),
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function walletPageSourceFile(relativeFile: string, fromUrl = import.meta.url): string {
|
|
33
|
+
return join(locateWalletPageSourceRoot(fromUrl), relativeFile)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function loadWalletPageRawSource(fromUrl = import.meta.url): string {
|
|
37
|
+
return walletPageSourceFiles(fromUrl).map(file => readFileSync(file, 'utf8')).join('\n')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function loadWalletPageSource(fromUrl = import.meta.url): string {
|
|
41
|
+
return stripWalletModuleSyntax(loadWalletPageRawSource(fromUrl))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function stripWalletModuleSyntax(source: string): string {
|
|
45
|
+
const out: string[] = []
|
|
46
|
+
let skippingImport = false
|
|
47
|
+
for (const line of source.split(/\r?\n/)) {
|
|
48
|
+
const trimmed = line.trim()
|
|
49
|
+
if (skippingImport) {
|
|
50
|
+
if (/\bfrom\s+['"][^'"]+['"]/.test(trimmed) || trimmed.endsWith(';')) skippingImport = false
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
if (trimmed.startsWith('import ')) {
|
|
54
|
+
if (!/\bfrom\s+['"][^'"]+['"]/.test(trimmed) && !trimmed.endsWith(';')) skippingImport = true
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
out.push(line.replace(/^export\s+(?=(async\s+function|const|let|function|interface|type|class)\b)/, ''))
|
|
58
|
+
}
|
|
59
|
+
return out.join('\n')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function locateWalletPageSourceRoot(fromUrl: string): string {
|
|
63
|
+
for (const candidate of walletPageSourceRootCandidates(fromUrl)) {
|
|
64
|
+
if (hasWalletPageSourceFiles(candidate)) return candidate
|
|
65
|
+
}
|
|
66
|
+
throw new Error('could not locate browser wallet page source files')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function walletPageSourceRootCandidates(fromUrl: string): string[] {
|
|
70
|
+
const start = dirname(fileURLToPath(fromUrl))
|
|
71
|
+
const candidates = [join(start, '..')]
|
|
72
|
+
for (let dir = start; ; dir = dirname(dir)) {
|
|
73
|
+
candidates.push(join(dir, 'src', 'identity', 'wallet'))
|
|
74
|
+
const parent = dirname(dir)
|
|
75
|
+
if (parent === dir) break
|
|
76
|
+
}
|
|
77
|
+
return Array.from(new Set(candidates))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hasWalletPageSourceFiles(sourceRoot: string): boolean {
|
|
81
|
+
return [
|
|
82
|
+
...WALLET_PAGE_MODULE_FILES,
|
|
83
|
+
WALLET_PAGE_ENTRY_FILE,
|
|
84
|
+
].every(file => existsSync(join(sourceRoot, file)))
|
|
85
|
+
}
|
|
@@ -11,12 +11,12 @@ import {
|
|
|
11
11
|
errorSlot,
|
|
12
12
|
getLastWalletError,
|
|
13
13
|
initializeViewElements,
|
|
14
|
-
serializeWalletError,
|
|
15
14
|
setState,
|
|
16
15
|
showPreparedMessage,
|
|
17
16
|
statusHint,
|
|
18
17
|
statusText,
|
|
19
18
|
} from './view.js'
|
|
19
|
+
import { serializeWalletError } from './errorView.js'
|
|
20
20
|
import {
|
|
21
21
|
buildTxParams,
|
|
22
22
|
clearCurrentWalletMethod,
|