ethagent 3.0.2 → 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.
Files changed (69) hide show
  1. package/README.md +6 -1
  2. package/package.json +3 -1
  3. package/src/app/FirstRun.tsx +1 -24
  4. package/src/app/firstRunConfig.ts +26 -0
  5. package/src/auth/openaiOAuth/landingPage.ts +2 -11
  6. package/src/chat/ChatScreen.tsx +15 -116
  7. package/src/chat/MessageList.tsx +18 -260
  8. package/src/chat/chatEnvironment.ts +16 -0
  9. package/src/chat/chatTurnContext.ts +50 -0
  10. package/src/chat/chatTurnOrchestrator.ts +5 -112
  11. package/src/chat/chatTurnRows.ts +64 -0
  12. package/src/chat/commands.ts +3 -178
  13. package/src/chat/continuityEditReview.ts +42 -0
  14. package/src/chat/input/ChatInput.tsx +10 -144
  15. package/src/chat/input/chatInputHelpers.ts +62 -0
  16. package/src/chat/input/inputRendering.tsx +93 -0
  17. package/src/chat/messageMarkdown.ts +220 -0
  18. package/src/chat/messageRows.ts +43 -0
  19. package/src/chat/planImplementation.ts +62 -0
  20. package/src/chat/slashCommandHandlers.ts +165 -0
  21. package/src/chat/slashCommandViews.ts +120 -0
  22. package/src/identity/continuity/challenges.ts +123 -0
  23. package/src/identity/continuity/envelope.ts +49 -1484
  24. package/src/identity/continuity/envelopeCreate.ts +322 -0
  25. package/src/identity/continuity/envelopeCrypto.ts +182 -0
  26. package/src/identity/continuity/envelopeParse.ts +441 -0
  27. package/src/identity/continuity/envelopeTypes.ts +204 -0
  28. package/src/identity/continuity/envelopeVersion.ts +1 -0
  29. package/src/identity/continuity/payloadNormalization.ts +183 -0
  30. package/src/identity/continuity/skills/loadSkills.ts +12 -69
  31. package/src/identity/continuity/skills/skillPaths.ts +76 -0
  32. package/src/identity/continuity/skillsNormalization.ts +119 -0
  33. package/src/identity/continuity/snapshotToken.ts +28 -0
  34. package/src/identity/hub/continuity/completion.ts +67 -0
  35. package/src/identity/hub/continuity/effects.ts +5 -62
  36. package/src/identity/hub/profile/effects.ts +6 -170
  37. package/src/identity/hub/profile/operatorSave.ts +202 -0
  38. package/src/identity/wallet/browserWallet/html.ts +1 -57
  39. package/src/identity/wallet/browserWallet/walletPageSource.ts +85 -0
  40. package/src/identity/wallet/page/controller.ts +1 -1
  41. package/src/identity/wallet/page/errorView.ts +122 -0
  42. package/src/identity/wallet/page/view.ts +3 -114
  43. package/src/mcp/manager.ts +8 -66
  44. package/src/mcp/managerHelpers.ts +70 -0
  45. package/src/models/ModelPicker.tsx +69 -889
  46. package/src/models/huggingface.ts +20 -137
  47. package/src/models/huggingfaceStorage.ts +136 -0
  48. package/src/models/llamacpp.ts +37 -303
  49. package/src/models/llamacppCommands.ts +44 -0
  50. package/src/models/llamacppConfig.ts +34 -0
  51. package/src/models/llamacppDiscovery.ts +176 -0
  52. package/src/models/llamacppOutput.ts +65 -0
  53. package/src/models/modelPickerCatalogFlow.ts +56 -0
  54. package/src/models/modelPickerCredentials.ts +166 -0
  55. package/src/models/modelPickerData.ts +41 -0
  56. package/src/models/modelPickerDisplay.tsx +132 -0
  57. package/src/models/modelPickerHfFlow.ts +192 -0
  58. package/src/models/modelPickerLocalRunnerFlow.ts +115 -0
  59. package/src/models/modelPickerTypes.ts +69 -0
  60. package/src/models/modelPickerUninstallFlow.ts +48 -0
  61. package/src/models/modelPickerViewHelpers.ts +174 -0
  62. package/src/providers/openai-chat.ts +5 -124
  63. package/src/providers/openaiChatWire.ts +124 -0
  64. package/src/runtime/providerTurn.ts +38 -0
  65. package/src/runtime/textToolParser.ts +161 -0
  66. package/src/runtime/toolIntent.ts +1 -1
  67. package/src/runtime/turn.ts +43 -499
  68. package/src/runtime/turnNudges.ts +223 -0
  69. 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,