ethagent 2.4.0 → 3.0.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.
Files changed (103) hide show
  1. package/README.md +7 -4
  2. package/package.json +2 -1
  3. package/src/app/FirstRun.tsx +155 -15
  4. package/src/app/FirstRunTimeline.tsx +4 -0
  5. package/src/app/input/AppInputProvider.tsx +19 -0
  6. package/src/app/input/appInputParser.ts +19 -4
  7. package/src/chat/ChatBottomPane.tsx +3 -1
  8. package/src/chat/ChatScreen.tsx +7 -1
  9. package/src/chat/ConversationStack.tsx +25 -19
  10. package/src/chat/MessageList.tsx +194 -53
  11. package/src/chat/chatSessionState.ts +1 -1
  12. package/src/chat/chatTurnOrchestrator.ts +59 -0
  13. package/src/chat/input/ChatInput.tsx +3 -0
  14. package/src/chat/input/textCursor.ts +13 -3
  15. package/src/chat/transcript/TranscriptView.tsx +7 -5
  16. package/src/chat/transcript/transcriptViewport.ts +88 -17
  17. package/src/chat/views/PermissionPrompt.tsx +26 -26
  18. package/src/chat/views/PermissionsView.tsx +18 -12
  19. package/src/chat/views/RewindView.tsx +3 -1
  20. package/src/cli/ResetConfirmView.tsx +24 -9
  21. package/src/identity/continuity/editor.ts +27 -2
  22. package/src/identity/continuity/envelope.ts +134 -9
  23. package/src/identity/continuity/publicSkills.ts +54 -1
  24. package/src/identity/continuity/skills/frontmatter.ts +183 -0
  25. package/src/identity/continuity/skills/loadSkills.ts +609 -0
  26. package/src/identity/continuity/skills/publicSkillsSync.ts +32 -0
  27. package/src/identity/continuity/skills/scaffold.ts +52 -0
  28. package/src/identity/continuity/skills/types.ts +30 -0
  29. package/src/identity/continuity/storage/defaults.ts +28 -47
  30. package/src/identity/continuity/storage/files.ts +1 -0
  31. package/src/identity/continuity/storage/paths.ts +1 -0
  32. package/src/identity/continuity/storage/scaffold.ts +25 -23
  33. package/src/identity/continuity/storage/status.ts +34 -5
  34. package/src/identity/continuity/storage/types.ts +3 -2
  35. package/src/identity/continuity/storage.ts +3 -0
  36. package/src/identity/hub/OperationalRoutes.tsx +79 -5
  37. package/src/identity/hub/Routes.tsx +5 -3
  38. package/src/identity/hub/continuity/ContinuityDashboardScreen.tsx +7 -73
  39. package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +6 -6
  40. package/src/identity/hub/continuity/SavePromptScreen.tsx +1 -2
  41. package/src/identity/hub/continuity/effects.ts +36 -5
  42. package/src/identity/hub/continuity/skills/DeleteSkillConfirmScreen.tsx +112 -0
  43. package/src/identity/hub/continuity/skills/NewSkillScreen.tsx +57 -0
  44. package/src/identity/hub/continuity/skills/NewSkillVisibilityScreen.tsx +52 -0
  45. package/src/identity/hub/continuity/skills/SkillActionsScreen.tsx +151 -0
  46. package/src/identity/hub/continuity/skills/SkillsTreeScreen.tsx +181 -0
  47. package/src/identity/hub/continuity/snapshot.ts +3 -0
  48. package/src/identity/hub/continuity/state.ts +9 -8
  49. package/src/identity/hub/continuity/vault.ts +42 -10
  50. package/src/identity/hub/create/CreateFlow.tsx +1 -1
  51. package/src/identity/hub/custody/CustodyEditFlow.tsx +3 -3
  52. package/src/identity/hub/custody/routes.tsx +1 -1
  53. package/src/identity/hub/ens/EnsEditAdvancedScreens.tsx +0 -1
  54. package/src/identity/hub/ens/EnsEditMaintenanceScreens.tsx +0 -1
  55. package/src/identity/hub/identityHubReducer.ts +15 -0
  56. package/src/identity/hub/profile/EditProfileFlow.tsx +5 -5
  57. package/src/identity/hub/profile/effects.ts +16 -3
  58. package/src/identity/hub/restore/RestoreFlow.tsx +43 -6
  59. package/src/identity/hub/restore/apply.ts +12 -1
  60. package/src/identity/hub/restore/recovery.ts +14 -4
  61. package/src/identity/hub/restore/resolve.ts +1 -1
  62. package/src/identity/hub/restore/useRestoreEffects.ts +4 -6
  63. package/src/identity/hub/shared/components/DetailsScreen.tsx +4 -1
  64. package/src/identity/hub/shared/components/IdentitySummary.tsx +118 -54
  65. package/src/identity/hub/shared/components/MenuScreen.tsx +21 -18
  66. package/src/identity/hub/shared/components/UnlinkedIdentityScreen.tsx +4 -4
  67. package/src/identity/hub/shared/components/menuFlagsFromReconciliation.ts +8 -12
  68. package/src/identity/hub/shared/effects/sync.ts +16 -3
  69. package/src/identity/hub/shared/model/copy.ts +2 -4
  70. package/src/identity/hub/transfer/effects.ts +15 -2
  71. package/src/identity/hub/useIdentityHubContinuity.ts +145 -23
  72. package/src/identity/hub/useIdentityHubController.ts +5 -1
  73. package/src/identity/hub/useIdentityHubSideEffects.ts +2 -4
  74. package/src/identity/wallet/page/copy.ts +43 -43
  75. package/src/mcp/manager.ts +1 -1
  76. package/src/models/ModelPicker.tsx +89 -84
  77. package/src/models/llamacpp.ts +160 -11
  78. package/src/models/llamacppPreflight.ts +1 -16
  79. package/src/models/modelPickerOptions.ts +45 -37
  80. package/src/providers/contracts.ts +1 -0
  81. package/src/providers/openai-chat.ts +50 -9
  82. package/src/providers/openai-responses.ts +19 -4
  83. package/src/runtime/toolExecution.ts +4 -3
  84. package/src/runtime/turn.ts +61 -30
  85. package/src/tools/changeDirectoryTool.ts +1 -1
  86. package/src/tools/contracts.ts +10 -0
  87. package/src/tools/deleteFileTool.ts +1 -1
  88. package/src/tools/editTool.ts +1 -1
  89. package/src/tools/listDirectoryTool.ts +1 -1
  90. package/src/tools/listSkillFilesTool.ts +77 -0
  91. package/src/tools/listSkillsTool.ts +68 -0
  92. package/src/tools/mcpResourceTools.ts +2 -2
  93. package/src/tools/privateContinuityReadTool.ts +1 -1
  94. package/src/tools/readSkillTool.ts +107 -0
  95. package/src/tools/readTool.ts +1 -1
  96. package/src/tools/registry.ts +6 -0
  97. package/src/tools/writeFileTool.ts +22 -2
  98. package/src/ui/Spinner.tsx +1 -1
  99. package/src/identity/continuity/localBackup.ts +0 -249
  100. package/src/identity/continuity/zipWriter.ts +0 -95
  101. package/src/identity/hub/continuity/index.ts +0 -7
  102. package/src/identity/hub/ens/index.ts +0 -11
  103. package/src/identity/hub/restore/index.ts +0 -22
@@ -0,0 +1,181 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import { Box, Text } from 'ink'
3
+ import { Surface } from '../../../../ui/Surface.js'
4
+ import { Select, type SelectOption } from '../../../../ui/Select.js'
5
+ import { theme } from '../../../../ui/theme.js'
6
+ import type { EthagentConfig, EthagentIdentity } from '../../../../storage/config.js'
7
+ import {
8
+ listSkillsTree,
9
+ type SkillsTreeView,
10
+ } from '../../../continuity/skills/loadSkills.js'
11
+ import type { SkillIndexEntry } from '../../../continuity/skills/types.js'
12
+ import { IdentitySummary } from '../../shared/components/IdentitySummary.js'
13
+ import type { ContinuityWorkingTreeStatus } from '../../../continuity/storage.js'
14
+
15
+ type SkillsTreeAction =
16
+ | { kind: 'skill'; relativePath: string }
17
+ | { kind: 'new' }
18
+ | { kind: 'open-folder' }
19
+ | { kind: 'noop' }
20
+ | { kind: 'back' }
21
+
22
+ interface SkillsTreeScreenProps {
23
+ identity?: EthagentIdentity
24
+ config?: EthagentConfig
25
+ workingStatus?: ContinuityWorkingTreeStatus | null
26
+ notice?: string
27
+ editorOpened?: boolean
28
+ footer: React.ReactNode
29
+ onOpenSkill: (relativePath: string) => void
30
+ onNewSkill: () => void
31
+ onOpenFolder: () => void
32
+ onBack: () => void
33
+ }
34
+
35
+ export const SkillsTreeScreen: React.FC<SkillsTreeScreenProps> = ({
36
+ identity,
37
+ config,
38
+ workingStatus,
39
+ notice,
40
+ editorOpened,
41
+ footer,
42
+ onOpenSkill,
43
+ onNewSkill,
44
+ onOpenFolder,
45
+ onBack,
46
+ }) => {
47
+ const [tree, setTree] = useState<SkillsTreeView | null>(null)
48
+ const [error, setError] = useState<string | null>(null)
49
+
50
+ useEffect(() => {
51
+ let cancelled = false
52
+ if (!identity) {
53
+ setTree({ skills: [], supportingCounts: {} })
54
+ return () => { cancelled = true }
55
+ }
56
+ const refresh = (): Promise<void> => listSkillsTree(identity)
57
+ .then(view => {
58
+ if (cancelled) return
59
+ setTree(view)
60
+ setError(null)
61
+ })
62
+ .catch(err => {
63
+ if (cancelled) return
64
+ setTree({ skills: [], supportingCounts: {} })
65
+ setError(String((err as Error).message ?? err))
66
+ })
67
+ void refresh()
68
+ if (!editorOpened) return () => { cancelled = true }
69
+ const interval = setInterval(() => { void refresh() }, 1500)
70
+ return () => {
71
+ cancelled = true
72
+ clearInterval(interval)
73
+ }
74
+ }, [identity, editorOpened])
75
+
76
+ const subtitle = notice ?? 'Open a skill, create one, or remove one.'
77
+ const isLoading = tree === null
78
+ const skills = tree?.skills ?? []
79
+ const supportingCounts = tree?.supportingCounts ?? {}
80
+ const hasAny = skills.length > 0
81
+
82
+ const options = buildOptions(skills, supportingCounts, isLoading, hasAny)
83
+
84
+ return (
85
+ <Surface title="Skills" subtitle={subtitle} footer={footer}>
86
+ <IdentitySummary identity={identity} config={config} workingStatus={workingStatus} compact />
87
+ {error && (
88
+ <Box marginTop={1}>
89
+ <Text color={theme.accentError}>{error}</Text>
90
+ </Box>
91
+ )}
92
+ {editorOpened && (
93
+ <Box marginTop={1}>
94
+ <Text color={theme.accentPeriwinkle}>Save with ctrl+s in your editor</Text>
95
+ </Box>
96
+ )}
97
+ <Box marginTop={1}>
98
+ <Select<SkillsTreeAction>
99
+ options={options}
100
+ hintLayout="inline"
101
+ onSubmit={choice => {
102
+ if (choice.kind === 'skill') return onOpenSkill(choice.relativePath)
103
+ if (choice.kind === 'new') return onNewSkill()
104
+ if (choice.kind === 'open-folder') return onOpenFolder()
105
+ if (choice.kind === 'back') return onBack()
106
+ }}
107
+ onCancel={onBack}
108
+ />
109
+ </Box>
110
+ </Surface>
111
+ )
112
+ }
113
+
114
+ function buildOptions(
115
+ entries: SkillIndexEntry[],
116
+ supportingCounts: Record<string, number>,
117
+ isLoading: boolean,
118
+ hasAny: boolean,
119
+ ): Array<SelectOption<SkillsTreeAction>> {
120
+ const rows: Array<SelectOption<SkillsTreeAction>> = []
121
+ const noopValue: SkillsTreeAction = { kind: 'noop' }
122
+
123
+ if (isLoading) {
124
+ rows.push({
125
+ value: noopValue,
126
+ role: 'notice',
127
+ label: 'Loading...',
128
+ labelColor: theme.dim,
129
+ indent: 0,
130
+ })
131
+ } else if (!hasAny) {
132
+ rows.push({ value: noopValue, role: 'section', label: 'Catalog' })
133
+ rows.push({ value: noopValue, role: 'notice', label: 'skills/', labelColor: theme.dim, indent: 3 })
134
+ rows.push({ value: noopValue, role: 'notice', label: '└── <skill>/', labelColor: theme.dim, indent: 3 })
135
+ rows.push({ value: noopValue, role: 'notice', label: ' └── SKILL.md', labelColor: theme.dim, indent: 3 })
136
+ } else {
137
+ rows.push({ value: noopValue, role: 'section', label: 'Catalog' })
138
+ rows.push({ value: noopValue, role: 'notice', label: 'skills/', labelColor: theme.dim, indent: 3 })
139
+ const sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name))
140
+ for (let i = 0; i < sorted.length; i++) {
141
+ const skill = sorted[i]
142
+ if (!skill) continue
143
+ const isLast = i === sorted.length - 1
144
+ const branch = isLast ? '└── ' : '├── '
145
+ const supportCount = supportingCounts[skill.name] ?? 0
146
+ const meta = [capitalize(skill.visibility)]
147
+ if (supportCount > 0) meta.push(`${supportCount + 1} files`)
148
+ rows.push({
149
+ value: { kind: 'skill', relativePath: skill.relativePath },
150
+ label: `${branch}${skill.name}/SKILL.md`,
151
+ hint: meta.join(' · '),
152
+ indent: 3,
153
+ })
154
+ }
155
+ }
156
+
157
+ rows.push({ value: noopValue, role: 'section', label: 'Manage' })
158
+ rows.push({
159
+ value: { kind: 'new' },
160
+ label: 'New Skill',
161
+ hint: 'Scaffold a new skill folder with SKILL.md',
162
+ })
163
+ rows.push({
164
+ value: { kind: 'open-folder' },
165
+ label: 'Open Skills Folder',
166
+ hint: 'Reveal skills/ in your file manager',
167
+ })
168
+ rows.push({
169
+ value: { kind: 'back' },
170
+ label: 'Back',
171
+ hint: 'Return to Identity Hub menu',
172
+ role: 'utility',
173
+ })
174
+
175
+ return rows
176
+ }
177
+
178
+ function capitalize(value: string): string {
179
+ if (!value) return value
180
+ return value.charAt(0).toUpperCase() + value.slice(1)
181
+ }
@@ -12,6 +12,7 @@ import {
12
12
  continuityAgentSnapshot,
13
13
  defaultContinuityFiles,
14
14
  } from '../../continuity/storage.js'
15
+ import type { ContinuitySkillsTree } from '../../continuity/envelope.js'
15
16
  import type { Erc8004RegistryConfig, EthagentOperatorsPointer } from '../../registry/erc8004.js'
16
17
  import { readOwnerAddressField } from '../../identityCompat.js'
17
18
  import { readCustodyMode } from '../custody/state.js'
@@ -206,6 +207,7 @@ export function createContinuityEnvelopeForSave(args: {
206
207
  walletSignature: string
207
208
  state: Record<string, unknown>
208
209
  files: ReturnType<typeof defaultContinuityFiles>
210
+ skills?: ContinuitySkillsTree
209
211
  walletAccess: WalletRestoreAccessContext
210
212
  challengePurpose?: WalletChallengePurpose
211
213
  }): ContinuitySnapshotEnvelope {
@@ -259,6 +261,7 @@ export function createContinuityEnvelopeForSave(args: {
259
261
  payload: {
260
262
  agent: continuityAgentSnapshot(args.identity),
261
263
  files: args.files,
264
+ ...(args.skills && Object.keys(args.skills).length > 0 ? { skills: args.skills } : {}),
262
265
  transcript: [],
263
266
  state: args.state,
264
267
  },
@@ -20,14 +20,15 @@ export function changedContinuitySnapshotFiles(
20
20
  workingStatus?: ContinuityWorkingTreeStatus | null,
21
21
  ): string[] {
22
22
  if (!workingStatus?.localContentHashes || !workingStatus.publishedContentHashes) return []
23
- const files: Array<keyof typeof workingStatus.localContentHashes> = ['SOUL.md', 'MEMORY.md', 'skills.json']
24
- return files
25
- .filter(file => workingStatus.localContentHashes?.[file] !== workingStatus.publishedContentHashes?.[file])
26
- .map(displayContinuitySnapshotFile)
27
- }
28
-
29
- function displayContinuitySnapshotFile(file: keyof NonNullable<ContinuityWorkingTreeStatus['localContentHashes']>): string {
30
- return file
23
+ const local = workingStatus.localContentHashes
24
+ const published = workingStatus.publishedContentHashes
25
+ const changed = (file: keyof typeof local): boolean =>
26
+ (local[file] ?? '') !== (published[file] ?? '')
27
+ const result: string[] = []
28
+ if (changed('SOUL.md')) result.push('SOUL.md')
29
+ if (changed('MEMORY.md')) result.push('MEMORY.md')
30
+ if (changed('skills.json') || changed('private-skills')) result.push('Skills')
31
+ return result
31
32
  }
32
33
 
33
34
  export function localChangeStatusView(
@@ -2,6 +2,7 @@ import { getAddress, type Address, type Hex } from 'viem'
2
2
  import type { EthagentIdentity } from '../../../storage/config.js'
3
3
  import {
4
4
  prepareSyncedIdentityMarkdownScaffold,
5
+ prepareSyncedSkillsTree,
5
6
  readContinuityFiles,
6
7
  readPublicSkillsFile,
7
8
  writeIdentityMarkdownScaffold,
@@ -10,13 +11,20 @@ import {
10
11
  import {
11
12
  createWalletRestoreAccessChallenge,
12
13
  serializeContinuitySnapshotEnvelope,
14
+ type ContinuityFiles,
15
+ type ContinuitySkillsTree,
13
16
  type WalletChallengePurpose,
14
17
  } from '../../continuity/envelope.js'
15
18
  import {
19
+ appendPublicSkillEntries,
16
20
  createAgentCard,
17
21
  defaultPublicSkillsProfile,
18
22
  serializeAgentCard,
19
23
  } from '../../continuity/publicSkills.js'
24
+ import {
25
+ derivePublicSkillEntries,
26
+ syncPublicSkillsManifest,
27
+ } from '../../continuity/skills/publicSkillsSync.js'
20
28
  import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
21
29
  import { addToIpfs, DEFAULT_IPFS_API_URL } from '../../storage/ipfs.js'
22
30
  import {
@@ -58,6 +66,11 @@ type VaultPublishPrepared = {
58
66
  nextIdentity: EthagentIdentity
59
67
  markdownScaffold?: IdentityMarkdownScaffold
60
68
  completionMessage: string
69
+ publishedSources: {
70
+ privateFiles: ContinuityFiles
71
+ publicSkills: string
72
+ skills: ContinuitySkillsTree
73
+ }
61
74
  }
62
75
 
63
76
  export async function runOperatorWalletRebackup(args: {
@@ -129,18 +142,22 @@ export async function runOperatorWalletRebackup(args: {
129
142
  const continuityFiles = markdownScaffold
130
143
  ? { 'SOUL.md': markdownScaffold['SOUL.md'], 'MEMORY.md': markdownScaffold['MEMORY.md'] }
131
144
  : await readContinuityFiles(nextIdentityForFiles)
132
- const publicSkillsJson = markdownScaffold
133
- ? markdownScaffold['skills.json']
134
- : await readPublicSkillsFile(nextIdentityForFiles)
145
+ const publicSkillsJson = await syncPublicSkillsManifest(nextIdentityForFiles)
135
146
  const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
136
147
  assertVerifiedPin(publicSkillsPin)
148
+ const publicSkillEntries = await derivePublicSkillEntries(nextIdentityForFiles)
149
+ const augmentedPublicProfile = appendPublicSkillEntries(
150
+ defaultPublicSkillsProfile(nextIdentityForFiles),
151
+ publicSkillEntries,
152
+ )
137
153
  const agentCardPin = await addToIpfs(
138
154
  DEFAULT_IPFS_API_URL,
139
- serializeAgentCard(createAgentCard(defaultPublicSkillsProfile(nextIdentityForFiles))),
155
+ serializeAgentCard(createAgentCard(augmentedPublicProfile)),
140
156
  fetch,
141
157
  { pinataJwt: step.pinataJwt },
142
158
  )
143
159
  assertVerifiedPin(agentCardPin)
160
+ const skillsTree = await prepareSyncedSkillsTree(nextIdentityForFiles)
144
161
  const envelope = createContinuityEnvelopeForSave({
145
162
  identity: nextIdentityForFiles,
146
163
  registry: step.registry,
@@ -149,6 +166,7 @@ export async function runOperatorWalletRebackup(args: {
149
166
  walletSignature: wallet.signature,
150
167
  state,
151
168
  files: continuityFiles,
169
+ skills: skillsTree,
152
170
  walletAccess,
153
171
  ...(challengePurpose ? { challengePurpose } : {}),
154
172
  })
@@ -185,7 +203,11 @@ export async function runOperatorWalletRebackup(args: {
185
203
  await writeIdentityMarkdownScaffold(nextIdentity, markdownScaffold)
186
204
  }
187
205
  await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'local operator-wallet snapshot' }).catch(() => null)
188
- await markCurrentContinuityFilesPublished(nextIdentity).catch(() => null)
206
+ await markCurrentContinuityFilesPublished(nextIdentity, {
207
+ privateFiles: continuityFiles,
208
+ publicSkills: publicSkillsJson,
209
+ skills: skillsTree,
210
+ }).catch(() => null)
189
211
  const completionMessage = nextEnsName !== undefined && nextEnsName !== ((step.identity.state as Record<string, unknown> | undefined)?.ensName as string | undefined)
190
212
  ? 'Snapshot saved locally. Owner wallet still needs to publish to make ENS changes discoverable.'
191
213
  : uploadedImageUri !== undefined
@@ -262,18 +284,22 @@ async function runOperatorWalletVaultPublish(args: {
262
284
  const continuityFiles = markdownScaffold
263
285
  ? { 'SOUL.md': markdownScaffold['SOUL.md'], 'MEMORY.md': markdownScaffold['MEMORY.md'] }
264
286
  : await readContinuityFiles(nextIdentityForFiles)
265
- const publicSkillsJson = markdownScaffold
266
- ? markdownScaffold['skills.json']
267
- : await readPublicSkillsFile(nextIdentityForFiles)
287
+ const publicSkillsJson = await syncPublicSkillsManifest(nextIdentityForFiles)
268
288
  const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
269
289
  assertVerifiedPin(publicSkillsPin)
290
+ const publicSkillEntries = await derivePublicSkillEntries(nextIdentityForFiles)
291
+ const augmentedPublicProfile = appendPublicSkillEntries(
292
+ defaultPublicSkillsProfile(nextIdentityForFiles),
293
+ publicSkillEntries,
294
+ )
270
295
  const agentCardPin = await addToIpfs(
271
296
  DEFAULT_IPFS_API_URL,
272
- serializeAgentCard(createAgentCard(defaultPublicSkillsProfile(nextIdentityForFiles))),
297
+ serializeAgentCard(createAgentCard(augmentedPublicProfile)),
273
298
  fetch,
274
299
  { pinataJwt: step.pinataJwt },
275
300
  )
276
301
  assertVerifiedPin(agentCardPin)
302
+ const skillsTree = await prepareSyncedSkillsTree(nextIdentityForFiles)
277
303
  const envelope = createContinuityEnvelopeForSave({
278
304
  identity: nextIdentityForFiles,
279
305
  registry: step.registry,
@@ -282,6 +308,7 @@ async function runOperatorWalletVaultPublish(args: {
282
308
  walletSignature: wallet.signature,
283
309
  state,
284
310
  files: continuityFiles,
311
+ skills: skillsTree,
285
312
  walletAccess,
286
313
  ...(challengePurpose ? { challengePurpose } : {}),
287
314
  })
@@ -354,6 +381,11 @@ async function runOperatorWalletVaultPublish(args: {
354
381
  nextIdentity,
355
382
  ...(markdownScaffold ? { markdownScaffold } : {}),
356
383
  completionMessage,
384
+ publishedSources: {
385
+ privateFiles: continuityFiles,
386
+ publicSkills: publicSkillsJson,
387
+ skills: skillsTree,
388
+ },
357
389
  },
358
390
  }
359
391
  },
@@ -373,6 +405,6 @@ async function runOperatorWalletVaultPublish(args: {
373
405
  await writeIdentityMarkdownScaffold(nextIdentity, result.prepared.markdownScaffold)
374
406
  }
375
407
  await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'operator-published snapshot' }).catch(() => null)
376
- await markCurrentContinuityFilesPublished(nextIdentity).catch(() => null)
408
+ await markCurrentContinuityFilesPublished(nextIdentity, result.prepared.publishedSources).catch(() => null)
377
409
  await callbacks.onIdentityComplete(nextIdentity, result.prepared.completionMessage, 'update')
378
410
  }
@@ -143,7 +143,7 @@ export const CreateFlow: React.FC<CreateFlowProps> = ({
143
143
  <BusyScreen
144
144
  title="Getting Ready"
145
145
  subtitle={indicator}
146
- label="checking IPFS storage and chain..."
146
+ label="checking IPFS storage and onchain..."
147
147
  onCancel={onBack}
148
148
  />
149
149
  )
@@ -214,7 +214,7 @@ export const CustodyEditFlow: React.FC<CustodyEditFlowProps> = ({
214
214
  <Surface
215
215
  title="Switch to Advanced"
216
216
  subtitle="Move this token into its own Vault so authorized operator wallets can update this agent onchain without your signature each time."
217
- footer={footerHint('enter confirm, esc back')}
217
+ footer={footerHint('enter confirm · esc back')}
218
218
  >
219
219
  <Box flexDirection="column">
220
220
  <Row label="Token" value={tokenLabel} />
@@ -236,7 +236,7 @@ export const CustodyEditFlow: React.FC<CustodyEditFlowProps> = ({
236
236
  { value: 'confirm', label: 'Yes, Switch to Advanced', hint: `Sign with ${shortAddress(ownerAddress || tokenOwner)} to deposit this token into its Vault` },
237
237
  { value: 'transfer', role: 'section', label: 'Move Token First' },
238
238
  { value: 'transfer', label: 'Prepare Token Transfer', hint: 'Move the token to a different wallet first, with snapshot handoff' },
239
- { value: 'back', role: 'section', label: 'Cancel' },
239
+ { value: 'back', role: 'section', label: 'Navigation' },
240
240
  { value: 'back', label: 'No, Go Back', hint: 'Return without changing custody', role: 'utility' },
241
241
  ]}
242
242
  hintLayout="inline"
@@ -278,7 +278,7 @@ export const CustodyEditFlow: React.FC<CustodyEditFlowProps> = ({
278
278
  options={[
279
279
  { value: 'confirm', role: 'section', label: 'Confirm' },
280
280
  { value: 'confirm', label: 'Yes, Switch to Simple', hint: `Sign with the owner wallet to unwrap ${tokenLabel} from its Vault` },
281
- { value: 'back', role: 'section', label: 'Cancel' },
281
+ { value: 'back', role: 'section', label: 'Navigation' },
282
282
  { value: 'back', label: 'No, Go Back', hint: 'Return without changing custody', role: 'utility' },
283
283
  ]}
284
284
  hintLayout="inline"
@@ -54,7 +54,7 @@ export function renderCustodyStep({
54
54
  footer={<Text color={theme.dim}>esc cancel</Text>}
55
55
  >
56
56
  <Box marginTop={1}>
57
- <Text color={theme.textSubtle}>Reading vault state from chain...</Text>
57
+ <Text color={theme.textSubtle}>Reading vault state from onchain...</Text>
58
58
  </Box>
59
59
  </Surface>
60
60
  )
@@ -19,7 +19,6 @@ import { shortAddress } from '../shared/model/format.js'
19
19
  import {
20
20
  footerHint,
21
21
  } from './EnsEditShared.js'
22
- import { IdentitySummary } from '../shared/components/IdentitySummary.js'
23
22
  import {
24
23
  EnsSetupBlockedScreen,
25
24
  EnsSetupReviewScreen,
@@ -19,7 +19,6 @@ import {
19
19
  EnsSetupRow,
20
20
  footerHint,
21
21
  } from './EnsEditShared.js'
22
- import { IdentitySummary } from '../shared/components/IdentitySummary.js'
23
22
  import { UnlinkEnsReviewScreen } from './EnsEditReviewScreens.js'
24
23
  import {
25
24
  DeleteSubdomainTxRunner,
@@ -59,6 +59,11 @@ export type Step =
59
59
  | { kind: 'public-profile-storage'; identity: EthagentIdentity; registry: Erc8004RegistryConfig; error?: string; pinataJwt?: string; profileUpdates?: ProfileUpdates; returnTo?: Step; vaultAddress?: `0x${string}` }
60
60
  | { kind: 'continuity-private'; notice?: string; editorOpened?: boolean }
61
61
  | { kind: 'continuity-public'; notice?: string; editorOpened?: boolean }
62
+ | { kind: 'continuity-skills-tree'; notice?: string; editorOpened?: boolean }
63
+ | { kind: 'continuity-skill-new'; error?: string }
64
+ | { kind: 'continuity-skill-new-visibility'; name: string; error?: string }
65
+ | { kind: 'continuity-skill-actions'; relativePath: string; notice?: string }
66
+ | { kind: 'continuity-skill-delete-confirm'; target: { kind: 'skill'; relativePath: string } }
62
67
  | { kind: 'rebackup-confirm'; back: Step }
63
68
  | { kind: 'recovery-refetch-confirm'; back: Step }
64
69
  | { kind: 'recovery-refetching'; identity: EthagentIdentity; registry: Erc8004RegistryConfig; back: Step }
@@ -173,6 +178,16 @@ function backStep(from: Step): Step {
173
178
  case 'continuity-private':
174
179
  case 'continuity-public':
175
180
  return { kind: 'menu' }
181
+ case 'continuity-skills-tree':
182
+ return { kind: 'menu' }
183
+ case 'continuity-skill-new':
184
+ return { kind: 'continuity-skills-tree' }
185
+ case 'continuity-skill-new-visibility':
186
+ return { kind: 'continuity-skill-new' }
187
+ case 'continuity-skill-actions':
188
+ return { kind: 'continuity-skills-tree' }
189
+ case 'continuity-skill-delete-confirm':
190
+ return { kind: 'continuity-skill-actions', relativePath: from.target.relativePath }
176
191
  case 'rebackup-confirm':
177
192
  case 'recovery-refetch-confirm':
178
193
  case 'recovery-refetching':
@@ -57,7 +57,7 @@ export const EditProfileFlow: React.FC<EditProfileFlowProps> = ({
57
57
  const currentName = step.name ?? readIdentityStateString(step.identity.state, 'name')
58
58
  return (
59
59
  <Surface
60
- title="Edit Name, Description, Icon"
60
+ title="Edit Profile"
61
61
  subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={1} />}
62
62
  footer={footerHint(EDIT_NEXT_FOOTER)}
63
63
  >
@@ -112,7 +112,7 @@ export const EditProfileFlow: React.FC<EditProfileFlowProps> = ({
112
112
  const draftDescription = step.description ?? currentDescription
113
113
  return (
114
114
  <Surface
115
- title="Edit Name, Description, Icon"
115
+ title="Edit Profile"
116
116
  subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={2} />}
117
117
  footer={footerHint(EDIT_DESCRIPTION_FOOTER)}
118
118
  >
@@ -145,7 +145,7 @@ const AgentIconStep: React.FC<{
145
145
  if (entryMode) {
146
146
  return (
147
147
  <Surface
148
- title="Edit Name, Description, Icon"
148
+ title="Edit Profile"
149
149
  subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={3} />}
150
150
  footer={footerHint(EDIT_NEXT_FOOTER)}
151
151
  >
@@ -165,7 +165,7 @@ const AgentIconStep: React.FC<{
165
165
 
166
166
  return (
167
167
  <Surface
168
- title="Edit Name, Description, Icon"
168
+ title="Edit Profile"
169
169
  subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={3} />}
170
170
  footer={footerHint('enter select · esc back')}
171
171
  >
@@ -208,7 +208,7 @@ const EditProfileReviewStep: React.FC<{
208
208
  const currentIcon = readIdentityStateString(step.identity.state, 'imageUrl')
209
209
  return (
210
210
  <Surface
211
- title="Edit Name, Description, Icon"
211
+ title="Edit Profile"
212
212
  subtitle={<FlowTimeline steps={EDIT_PROFILE_STEPS} current={4} />}
213
213
  footer={footerHint('enter save · esc back')}
214
214
  >
@@ -6,15 +6,21 @@ import {
6
6
  type WalletChallengePurpose,
7
7
  } from '../../continuity/envelope.js'
8
8
  import {
9
+ prepareSyncedSkillsTree,
9
10
  prepareSyncedPublicSkillsJson,
10
11
  readContinuityFiles,
11
12
  writePublicSkillsFile,
12
13
  } from '../../continuity/storage.js'
13
14
  import {
15
+ appendPublicSkillEntries,
14
16
  createAgentCard,
15
17
  defaultPublicSkillsProfile,
16
18
  serializeAgentCard,
17
19
  } from '../../continuity/publicSkills.js'
20
+ import {
21
+ derivePublicSkillEntries,
22
+ syncPublicSkillsManifest,
23
+ } from '../../continuity/skills/publicSkillsSync.js'
18
24
  import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
19
25
  import { addToIpfs, DEFAULT_IPFS_API_URL, isPinataUploadUrl } from '../../storage/ipfs.js'
20
26
  import {
@@ -151,7 +157,7 @@ async function runPublicProfileSigningInner(
151
157
  includeLastBackedUpAt: false,
152
158
  })
153
159
  const nextIdentityForFiles: EthagentIdentity = { ...step.identity, state }
154
- const publicSkillsJson = await prepareSyncedPublicSkillsJson(nextIdentityForFiles)
160
+ const publicSkillsJson = await syncPublicSkillsManifest(nextIdentityForFiles)
155
161
  const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
156
162
  assertVerifiedPin(publicSkillsPin)
157
163
  const agentCardPin = await addToIpfs(
@@ -395,18 +401,24 @@ async function prepareOperatorProfileArtifacts(args: {
395
401
  })
396
402
  const nextIdentityForFiles: EthagentIdentity = { ...step.identity, state }
397
403
 
398
- const publicSkillsJson = await prepareSyncedPublicSkillsJson(nextIdentityForFiles)
404
+ const publicSkillsJson = await syncPublicSkillsManifest(nextIdentityForFiles)
399
405
  const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
400
406
  assertVerifiedPin(publicSkillsPin)
407
+ const publicSkillEntries = await derivePublicSkillEntries(nextIdentityForFiles)
408
+ const augmentedPublicProfile = appendPublicSkillEntries(
409
+ defaultPublicSkillsProfile(nextIdentityForFiles),
410
+ publicSkillEntries,
411
+ )
401
412
  const agentCardPin = await addToIpfs(
402
413
  DEFAULT_IPFS_API_URL,
403
- serializeAgentCard(createAgentCard(defaultPublicSkillsProfile(nextIdentityForFiles))),
414
+ serializeAgentCard(createAgentCard(augmentedPublicProfile)),
404
415
  fetch,
405
416
  { pinataJwt: step.pinataJwt },
406
417
  )
407
418
  assertVerifiedPin(agentCardPin)
408
419
 
409
420
  const continuityFiles = await readContinuityFiles(nextIdentityForFiles)
421
+ const skillsTree = await prepareSyncedSkillsTree(nextIdentityForFiles)
410
422
  const envelope = createContinuityEnvelopeForSave({
411
423
  identity: nextIdentityForFiles,
412
424
  registry: step.registry,
@@ -415,6 +427,7 @@ async function prepareOperatorProfileArtifacts(args: {
415
427
  walletSignature: wallet.signature,
416
428
  state,
417
429
  files: continuityFiles,
430
+ skills: skillsTree,
418
431
  walletAccess,
419
432
  challengePurpose,
420
433
  })