ethagent 1.0.8 → 1.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 (30) hide show
  1. package/package.json +1 -2
  2. package/src/chat/ChatScreen.tsx +5 -4
  3. package/src/chat/ContinuityEditReviewView.tsx +3 -3
  4. package/src/chat/commands.ts +5 -5
  5. package/src/cli/ResetConfirmView.tsx +12 -13
  6. package/src/cli/main.tsx +27 -30
  7. package/src/cli/reset.ts +7 -8
  8. package/src/cli/updateNotice.ts +52 -0
  9. package/src/identity/continuity/envelope.ts +11 -5
  10. package/src/identity/continuity/storage.ts +1 -1
  11. package/src/identity/hub/IdentityHub.tsx +6 -7
  12. package/src/identity/hub/identityHubModel.ts +12 -12
  13. package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +39 -34
  14. package/src/identity/hub/screens/CreateFlow.tsx +4 -4
  15. package/src/identity/hub/screens/DetailsScreen.tsx +2 -2
  16. package/src/identity/hub/screens/EditProfileFlow.tsx +5 -5
  17. package/src/identity/hub/screens/ErrorScreen.tsx +2 -2
  18. package/src/identity/hub/screens/IdentitySummary.tsx +32 -12
  19. package/src/identity/hub/screens/MenuScreen.tsx +17 -17
  20. package/src/identity/hub/screens/NetworkScreen.tsx +7 -3
  21. package/src/identity/hub/screens/RecoveryConfirmScreen.tsx +9 -7
  22. package/src/identity/hub/screens/RestoreFlow.tsx +2 -2
  23. package/src/identity/hub/screens/StorageCredentialScreen.tsx +5 -5
  24. package/src/identity/wallet/wallet-page/wallet.html +1095 -966
  25. package/src/models/ModelPicker.tsx +71 -71
  26. package/src/models/llamacppPreflight.ts +1 -1
  27. package/src/models/modelPickerOptions.ts +22 -22
  28. package/src/storage/factoryReset.ts +17 -20
  29. package/src/tools/privateContinuityEditTool.ts +1 -1
  30. package/src/ui/BrandSplash.tsx +7 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ethagent",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "A privacy-first AI agent with a portable Ethereum identity",
5
5
  "type": "module",
6
6
  "main": "bin/ethagent.js",
@@ -47,7 +47,6 @@
47
47
  "ink": "^6.8.0",
48
48
  "react": "^19.2.4",
49
49
  "tsx": "^4.21.0",
50
- "update-notifier": "^7.3.1",
51
50
  "viem": "^2.48.4",
52
51
  "zod": "^3.25.76"
53
52
  },
@@ -79,6 +79,7 @@ import { EMPTY_MCP_SNAPSHOT, McpManager, type McpSnapshot } from '../mcp/manager
79
79
  type ChatScreenProps = {
80
80
  config: EthagentConfig
81
81
  onReplaceConfig?: (next: EthagentConfig) => void
82
+ updateNotice?: string | null
82
83
  }
83
84
 
84
85
  type PendingPlan = {
@@ -122,7 +123,7 @@ async function ensureLocalProviderReady(config: EthagentConfig): Promise<{ ok: t
122
123
  return { ok: true }
123
124
  }
124
125
 
125
- export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, onReplaceConfig }) => {
126
+ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, onReplaceConfig, updateNotice }) => {
126
127
  useRegisterKeybindingContext('Chat')
127
128
  const { exit } = useApp()
128
129
  const [config, setConfig] = useState<EthagentConfig>(initialConfig)
@@ -1109,7 +1110,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
1109
1110
  }
1110
1111
  overlayRef.current = 'none'
1111
1112
  setOverlay('none')
1112
- pushNote('snapshot not published yet.', 'dim')
1113
+ pushNote('snapshot not saved yet.', 'dim')
1113
1114
  },
1114
1115
  [continuityEditReview, pushNote],
1115
1116
  )
@@ -1118,7 +1119,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
1118
1119
  setContinuityEditReview(null)
1119
1120
  overlayRef.current = 'none'
1120
1121
  setOverlay('none')
1121
- pushNote('snapshot not published yet.', 'dim')
1122
+ pushNote('snapshot not saved yet.', 'dim')
1122
1123
  }, [pushNote])
1123
1124
 
1124
1125
  const handleCopyDone = useCallback(
@@ -1402,7 +1403,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
1402
1403
  </Text>
1403
1404
  </Box>
1404
1405
  )
1405
- const header = <BrandSplash contextLine={contextLine} tipLine={tipLine} />
1406
+ const header = <BrandSplash contextLine={contextLine} tipLine={tipLine} updateNotice={updateNotice ?? null} />
1406
1407
  return (
1407
1408
  <ConversationStack
1408
1409
  header={header}
@@ -19,7 +19,7 @@ export const ContinuityEditReviewView: React.FC<{
19
19
  }> = ({ review, onSelect, onCancel }) => (
20
20
  <Surface
21
21
  title="Private Continuity Updated"
22
- subtitle="Review the file, then publish an encrypted snapshot."
22
+ subtitle="Review the file, then save an encrypted snapshot."
23
23
  footer="enter select · esc later"
24
24
  >
25
25
  <Box flexDirection="column">
@@ -37,8 +37,8 @@ export const ContinuityEditReviewView: React.FC<{
37
37
  <Select<ContinuityEditReviewAction>
38
38
  options={[
39
39
  { value: 'open', label: `open ${review.file}`, hint: 'review the edited private file now' },
40
- { value: 'save-publish', label: 'publish snapshot now', hint: 'go directly to wallet approval' },
41
- { value: 'later', label: 'later', hint: 'keep the local draft unpublished' },
40
+ { value: 'save-publish', label: 'save snapshot now', hint: 'go directly to wallet approval' },
41
+ { value: 'later', label: 'later', hint: 'keep the local draft unsaved' },
42
42
  ]}
43
43
  onSubmit={onSelect}
44
44
  onCancel={onCancel}
@@ -154,7 +154,7 @@ const COMMANDS: CommandSpec[] = [
154
154
  if (installed.length === 0) {
155
155
  return {
156
156
  kind: 'note',
157
- text: 'no local model files downloaded. open alt+p and choose "add local model file".',
157
+ text: 'No local model files downloaded. Open Alt+P and choose "Add Local Model File".',
158
158
  }
159
159
  }
160
160
  const lines = installed.map(m => {
@@ -190,7 +190,7 @@ const COMMANDS: CommandSpec[] = [
190
190
  return {
191
191
  kind: 'note',
192
192
  variant: 'error',
193
- text: `'${name}' is not downloaded. open alt+p and choose "view full catalog" or "add local model file".`,
193
+ text: `'${name}' is not downloaded. Open Alt+P and choose "View Full Catalog" or "Add Local Model File".`,
194
194
  }
195
195
  }
196
196
  } else {
@@ -364,7 +364,7 @@ async function runHuggingFace(args: string, ctx: SlashContext): Promise<SlashRes
364
364
  return {
365
365
  kind: 'note',
366
366
  variant: 'dim',
367
- text: 'no local model files downloaded. press alt+p and choose "add local model file".',
367
+ text: 'No local model files downloaded. Press Alt+P and choose "Add Local Model File".',
368
368
  }
369
369
  }
370
370
  const lines = installed.map(model => {
@@ -382,8 +382,8 @@ async function runHuggingFace(args: string, ctx: SlashContext): Promise<SlashRes
382
382
  kind: 'note',
383
383
  variant: 'dim',
384
384
  text: link
385
- ? `alt+p opened. choose "add local model file" and paste: ${link}`
386
- : 'alt+p opened. choose "add local model file" and paste the model URL or repo id.',
385
+ ? `Alt+P opened. Choose "Add Local Model File" and paste: ${link}`
386
+ : 'Alt+P opened. Choose "Add Local Model File" and paste the model URL or repo ID.',
387
387
  }
388
388
  }
389
389
 
@@ -16,27 +16,26 @@ export const ResetConfirmView: React.FC<{
16
16
  }
17
17
 
18
18
  return (
19
- <Surface title="reset ethagent?" subtitle="are you sure? this only affects this machine." footer="enter select · esc cancel">
19
+ <Surface title="Reset Local Data?" subtitle="Deletes this machine's ethagent data. Models and onchain records stay." footer="enter select · esc cancel">
20
20
  <Box flexDirection="column">
21
- <Section title="will delete" lines={[
21
+ <Section title="Deletes" lines={[
22
+ 'Identity files, sessions, history, credentials',
22
23
  localDataLine(plan.deletePaths.length),
23
- 'identity metadata, markdown vaults, sessions, prompt history',
24
- 'rewind history, permissions, credentials',
25
24
  ]} />
26
- <Section title="will keep" lines={[
27
- 'installed local LLM assets',
28
- ...(plan.preservedPaths.length > 0 ? [`${plan.preservedPaths.length} local model path${plan.preservedPaths.length === 1 ? '' : 's'}`] : ['no local model assets found']),
25
+ <Section title="Keeps" lines={[
26
+ 'Local GGUF models and llama.cpp runners',
27
+ ...(plan.preservedPaths.length > 0 ? [`${plan.preservedPaths.length} local model path${plan.preservedPaths.length === 1 ? '' : 's'}`] : ['No local model assets found']),
29
28
  ]} />
30
- <Section title="not touched" lines={[
31
- 'onchain agent tokens',
32
- 'IPFS-pinned snapshots and public metadata',
29
+ <Section title="Not Touched" lines={[
30
+ 'ERC-8004 tokens and onchain records',
31
+ 'IPFS snapshots and public metadata',
33
32
  ]} />
34
33
  </Box>
35
34
  <Box marginTop={1}>
36
35
  <Select<'confirm' | 'cancel'>
37
36
  options={[
38
- { value: 'confirm', label: 'reset local data', hint: 'delete local ethagent data now' },
39
- { value: 'cancel', label: 'cancel', hint: 'leave local data unchanged' },
37
+ { value: 'confirm', label: 'Reset Local Data', hint: 'Delete local ethagent data now' },
38
+ { value: 'cancel', label: 'Cancel', hint: 'Leave local data unchanged' },
40
39
  ]}
41
40
  onSubmit={choice => finish(choice === 'confirm')}
42
41
  onCancel={() => finish(false)}
@@ -56,6 +55,6 @@ const Section: React.FC<{ title: string; lines: string[] }> = ({ title, lines })
56
55
  )
57
56
 
58
57
  function localDataLine(count: number): string {
59
- if (count === 0) return 'no local ethagent data found'
58
+ if (count === 0) return 'No local ethagent data found'
60
59
  return `${count} local path${count === 1 ? '' : 's'} under ~/.ethagent`
61
60
  }
package/src/cli/main.tsx CHANGED
@@ -12,29 +12,10 @@ import { AppInputProvider, useAppInput } from '../app/input/AppInputProvider.js'
12
12
  import { loadConfig, type EthagentConfig } from '../storage/config.js'
13
13
  import { runResetCommand } from './reset.js'
14
14
  import { runPreviewCommand } from './preview.js'
15
+ import { checkForUpdates } from './updateNotice.js'
16
+ import { Spinner } from '../ui/Spinner.js'
15
17
 
16
18
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
17
- const pkgPath = path.resolve(__dirname, '..', '..', 'package.json')
18
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
19
-
20
- async function checkForUpdates() {
21
- try {
22
- const res = await fetch('https://registry.npmjs.org/ethagent/latest', { signal: AbortSignal.timeout(1200) })
23
- const { version: latest } = await res.json() as { version: string }
24
- if (latest && latest !== pkg.version) {
25
- const line1 = ` Update available: ${theme.accentPrimary}${pkg.version}${theme.text} -> ${theme.accentMint}${latest}${theme.text} `
26
- const line2 = ` Run ${theme.accentPeach}npm install -g ethagent${theme.text} to update `
27
- const border = theme.dim + '─'.repeat(Math.max(line1.length - 20, line2.length - 20) + 10) + theme.text
28
-
29
- process.stdout.write(`\n${theme.dim}┌${border}┐${theme.text}\n`)
30
- process.stdout.write(`${theme.dim}│${theme.text} ${line1} ${theme.dim}│${theme.text}\n`)
31
- process.stdout.write(`${theme.dim}│${theme.text} ${line2} ${theme.dim}│${theme.text}\n`)
32
- process.stdout.write(`${theme.dim}└${border}┘${theme.text}\n\n`)
33
- }
34
- } catch {
35
- // Silent fail for offline/timeout
36
- }
37
- }
38
19
 
39
20
  function readVersion(): string {
40
21
  try {
@@ -70,17 +51,24 @@ type AppPhase =
70
51
  | { kind: 'cancelled' }
71
52
  | { kind: 'error'; message: string }
72
53
 
73
- const AppRoot: React.FC<{ setExitCode: (code: number) => void }> = ({ setExitCode }) => {
54
+ const MIN_STARTUP_SPINNER_MS = 480
55
+
56
+ function delay(ms: number): Promise<void> {
57
+ return new Promise(resolve => setTimeout(resolve, ms))
58
+ }
59
+
60
+ const AppRoot: React.FC<{ setExitCode: (code: number) => void; currentVersion: string }> = ({ setExitCode, currentVersion }) => {
74
61
  const [phase, setPhase] = useState<AppPhase>({ kind: 'loading' })
62
+ const [updateNotice, setUpdateNotice] = useState<string | null>(null)
75
63
  const { exit } = useApp()
76
64
 
77
65
  useEffect(() => {
78
66
  if (phase.kind !== 'loading') return
79
67
  let cancelled = false
80
- loadConfig()
68
+ Promise.all([loadConfig(), delay(MIN_STARTUP_SPINNER_MS)])
81
69
  .then(config => {
82
70
  if (cancelled) return
83
- setPhase(config ? { kind: 'ready', config } : { kind: 'setup' })
71
+ setPhase(config[0] ? { kind: 'ready', config: config[0] } : { kind: 'setup' })
84
72
  })
85
73
  .catch((err: unknown) => {
86
74
  if (cancelled) return
@@ -89,6 +77,16 @@ const AppRoot: React.FC<{ setExitCode: (code: number) => void }> = ({ setExitCod
89
77
  return () => { cancelled = true }
90
78
  }, [phase])
91
79
 
80
+ useEffect(() => {
81
+ let cancelled = false
82
+ void checkForUpdates(currentVersion)
83
+ .then(notice => {
84
+ if (!cancelled) setUpdateNotice(notice)
85
+ })
86
+ .catch(() => {})
87
+ return () => { cancelled = true }
88
+ }, [currentVersion])
89
+
92
90
  useEffect(() => {
93
91
  if (phase.kind === 'cancelled') {
94
92
  setExitCode(1)
@@ -111,7 +109,7 @@ const AppRoot: React.FC<{ setExitCode: (code: number) => void }> = ({ setExitCod
111
109
  if (phase.kind === 'loading') {
112
110
  return (
113
111
  <Box padding={1}>
114
- <Text color={theme.dim}>Preparing session...</Text>
112
+ <Spinner label="Starting ethagent..." showElapsed={false} />
115
113
  </Box>
116
114
  )
117
115
  }
@@ -141,16 +139,17 @@ const AppRoot: React.FC<{ setExitCode: (code: number) => void }> = ({ setExitCod
141
139
  <ChatScreen
142
140
  config={phase.config}
143
141
  onReplaceConfig={next => setPhase({ kind: 'ready', config: next })}
142
+ updateNotice={updateNotice}
144
143
  />
145
144
  )
146
145
  }
147
146
 
148
- async function runDefault(): Promise<number> {
147
+ async function runDefault(currentVersion: string): Promise<number> {
149
148
  let exitCode = 0
150
149
  const instance = render(
151
150
  <AppInputProvider>
152
151
  <KeybindingProvider>
153
- <AppRoot setExitCode={code => { exitCode = code }} />
152
+ <AppRoot setExitCode={code => { exitCode = code }} currentVersion={currentVersion} />
154
153
  </KeybindingProvider>
155
154
  </AppInputProvider>,
156
155
  {
@@ -169,9 +168,7 @@ async function main(): Promise<number> {
169
168
  const argv = process.argv.slice(2)
170
169
  const [cmd, ...rest] = argv
171
170
 
172
- await checkForUpdates()
173
-
174
- if (!cmd) return runDefault()
171
+ if (!cmd) return runDefault(readVersion())
175
172
  if (cmd === '--version' || cmd === '-v') {
176
173
  process.stdout.write(`ethagent ${readVersion()}\n`)
177
174
  return 0
package/src/cli/reset.ts CHANGED
@@ -37,7 +37,7 @@ export async function runResetCommand(args: string[] = [], io: ResetCommandIO =
37
37
  ? await readTextConfirmation(plan, io)
38
38
  : await readInkConfirmation(plan, io)
39
39
  if (!confirmed) {
40
- write('factory reset cancelled.\n')
40
+ write('Reset Cancelled.\n')
41
41
  return 1
42
42
  }
43
43
  } else {
@@ -46,12 +46,12 @@ export async function runResetCommand(args: string[] = [], io: ResetCommandIO =
46
46
 
47
47
  const result = await runFactoryReset({ clearSecrets: io.clearSecrets })
48
48
  write([
49
- 'factory reset complete.',
50
- `deleted ${result.deletedPaths.length} local path${result.deletedPaths.length === 1 ? '' : 's'}.`,
51
- `cleared ${result.clearedSecretAccounts.length} known secret account${result.clearedSecretAccounts.length === 1 ? '' : 's'}.`,
49
+ 'Reset Complete.',
50
+ `Deleted ${result.deletedPaths.length} local path${result.deletedPaths.length === 1 ? '' : 's'}.`,
51
+ `Cleared ${result.clearedSecretAccounts.length} secret account${result.clearedSecretAccounts.length === 1 ? '' : 's'}.`,
52
52
  result.preservedPaths.length > 0
53
- ? `preserved local LLM assets: ${result.preservedPaths.length} path${result.preservedPaths.length === 1 ? '' : 's'}.`
54
- : 'no local model assets were present.',
53
+ ? `Kept ${result.preservedPaths.length} local model path${result.preservedPaths.length === 1 ? '' : 's'}.`
54
+ : 'Kept no local model assets.',
55
55
  '',
56
56
  ].join('\n'))
57
57
  return 0
@@ -90,7 +90,6 @@ async function readInkConfirmation(plan: FactoryResetPlan, io: ResetCommandIO):
90
90
 
91
91
  async function readConfirmation(io: ResetCommandIO): Promise<string> {
92
92
  if (io.readConfirmation) {
93
- ;(io.write ?? (text => { processStdout.write(text) }))('type confirm to wipe local ethagent data: ')
94
93
  return io.readConfirmation()
95
94
  }
96
95
 
@@ -99,7 +98,7 @@ async function readConfirmation(io: ResetCommandIO): Promise<string> {
99
98
  output: io.output ?? processStdout,
100
99
  })
101
100
  try {
102
- return await rl.question('type confirm to wipe local ethagent data: ')
101
+ return await rl.question('Confirm reset: ')
103
102
  } finally {
104
103
  rl.close()
105
104
  }
@@ -0,0 +1,52 @@
1
+ type RegistryResponse = {
2
+ ok?: boolean
3
+ json: () => Promise<unknown>
4
+ }
5
+
6
+ type RegistryFetch = (url: string, init: { signal: AbortSignal }) => Promise<RegistryResponse>
7
+
8
+ const REGISTRY_LATEST_URL = 'https://registry.npmjs.org/ethagent/latest'
9
+
10
+ export function compareVersions(left: string, right: string): number {
11
+ const a = parseVersion(left)
12
+ const b = parseVersion(right)
13
+ if (!a || !b) return 0
14
+ for (let i = 0; i < 3; i++) {
15
+ if (a[i] > b[i]) return 1
16
+ if (a[i] < b[i]) return -1
17
+ }
18
+ return 0
19
+ }
20
+
21
+ export function isNewerVersion(candidate: string, current: string): boolean {
22
+ return compareVersions(candidate, current) > 0
23
+ }
24
+
25
+ export function formatUpdateNotice(currentVersion: string, latestVersion: string): string | null {
26
+ if (!isNewerVersion(latestVersion, currentVersion)) return null
27
+ return `update available: ethagent ${currentVersion} -> ${latestVersion}; run npm i -g ethagent`
28
+ }
29
+
30
+ export async function checkForUpdates(
31
+ currentVersion: string,
32
+ options: { fetchImpl?: RegistryFetch; timeoutMs?: number } = {},
33
+ ): Promise<string | null> {
34
+ const fetchImpl = options.fetchImpl ?? fetch
35
+ const timeoutMs = options.timeoutMs ?? 1200
36
+ try {
37
+ const res = await fetchImpl(REGISTRY_LATEST_URL, { signal: AbortSignal.timeout(timeoutMs) })
38
+ if (res.ok === false) return null
39
+ const body = await res.json()
40
+ if (!body || typeof body !== 'object') return null
41
+ const latest = (body as { version?: unknown }).version
42
+ return typeof latest === 'string' ? formatUpdateNotice(currentVersion, latest) : null
43
+ } catch {
44
+ return null
45
+ }
46
+ }
47
+
48
+ function parseVersion(value: string): [number, number, number] | null {
49
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(value.trim())
50
+ if (!match) return null
51
+ return [Number(match[1]), Number(match[2]), Number(match[3])]
52
+ }
@@ -79,15 +79,21 @@ export class ContinuitySnapshotOwnerMismatchError extends Error {
79
79
  }
80
80
  }
81
81
 
82
+ export const CONTINUITY_SNAPSHOT_CHALLENGE_MESSAGES = [
83
+ 'ethagent: save or restore identity files',
84
+ 'Action: encrypt or decrypt local identity files',
85
+ 'Private: SOUL.md, MEMORY.md',
86
+ 'Public: skills.json, public profile',
87
+ 'Safety: no transaction, spending, or approvals',
88
+ 'Version: 1',
89
+ ] as const
90
+
82
91
  export function createContinuitySnapshotChallenge(ownerAddress: string): string {
83
92
  const checksum = toChecksumAddress(ownerAddress)
84
93
  return [
85
- 'ethagent private continuity',
94
+ CONTINUITY_SNAPSHOT_CHALLENGE_MESSAGES[0],
86
95
  `Owner: ${checksum}`,
87
- 'Purpose: unlock the encrypted SOUL.md and MEMORY.md snapshot for this device',
88
- 'Scope: read and restore private agent continuity only',
89
- 'Safety: this signature does not send a transaction, spend funds, or grant token approval',
90
- 'Version: 1',
96
+ ...CONTINUITY_SNAPSHOT_CHALLENGE_MESSAGES.slice(1),
91
97
  ].join('\n')
92
98
  }
93
99
 
@@ -287,7 +287,7 @@ export function defaultContinuityFiles(identity: EthagentIdentity, now = new Dat
287
287
  '## Private Instructions',
288
288
  '',
289
289
  '- Keep owner-specific standing instructions in this file.',
290
- '- Do not publish this file directly; use encrypted snapshot backup from Identity Hub.',
290
+ '- Do not share this file directly; save it via the Identity Hub encrypted snapshot.',
291
291
  '- Public capabilities belong in skills.json.',
292
292
  '',
293
293
  '## Boundaries',
@@ -196,7 +196,8 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
196
196
 
197
197
  useEffect(() => {
198
198
  let cancelled = false
199
- if (!identity || step.kind !== 'menu') return
199
+ if (!identity) return
200
+ if (step.kind !== 'menu' && step.kind !== 'continuity-private' && step.kind !== 'continuity-public') return
200
201
 
201
202
  const checkStatus = async () => {
202
203
  try {
@@ -591,7 +592,7 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
591
592
  return (
592
593
  <WalletApprovalScreen
593
594
  title="Refetch Latest Snapshot"
594
- subtitle="Wallet approval decrypts the latest published snapshot and overwrites local SOUL.md, MEMORY.md, and skills.json."
595
+ subtitle="Wallet approval decrypts the latest saved snapshot and overwrites local SOUL.md, MEMORY.md, and skills.json."
595
596
  walletSession={walletSession}
596
597
  label={restoreProgress?.label ?? 'fetching latest snapshot from chain...'}
597
598
  onCancel={() => setStep(step.back)}
@@ -604,13 +605,12 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
604
605
  <PrivateContinuityScreen
605
606
  identity={identity}
606
607
  config={config}
608
+ workingStatus={workingStatus}
607
609
  ready={continuityReady}
608
610
  notice={step.notice}
609
- canBackup={canRebackup}
610
611
  footer={footer}
611
612
  onOpenSoul={() => { void openContinuityFile('soul') }}
612
613
  onOpenMemory={() => { void openContinuityFile('memory') }}
613
- onBackup={() => setStep({ kind: 'rebackup-confirm', back: { kind: 'continuity-private' } })}
614
614
  onBack={back}
615
615
  />
616
616
  )
@@ -621,13 +621,12 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
621
621
  <PublicSkillsScreen
622
622
  identity={identity}
623
623
  config={config}
624
+ workingStatus={workingStatus}
624
625
  ready={continuityReady}
625
626
  notice={step.notice}
626
- canPublish={canRebackup}
627
627
  footer={footer}
628
628
  onEditProfile={() => openPublicProfileEdit({ kind: 'continuity-public' })}
629
629
  onOpenSkills={() => { void openContinuityFile('skills') }}
630
- onPublish={() => setStep({ kind: 'rebackup-confirm', back: { kind: 'continuity-public' } })}
631
630
  onBack={back}
632
631
  />
633
632
  )
@@ -784,7 +783,7 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
784
783
 
785
784
  async function readPublishedPublicSkills(identity: EthagentIdentity): Promise<string> {
786
785
  const cid = identity.publicSkills?.cid
787
- if (!cid) throw new Error('no published public skills CID')
786
+ if (!cid) throw new Error('no saved public skills CID')
788
787
  return new TextDecoder().decode(await catFromIpfs(
789
788
  identity.backup?.ipfsApiUrl ?? DEFAULT_IPFS_API_URL,
790
789
  cid,
@@ -93,7 +93,7 @@ export function tokenLabel(candidate: Erc8004AgentCandidate): string {
93
93
  }
94
94
 
95
95
  export function tokenCandidateLabel(candidate: Erc8004AgentCandidate): string {
96
- return candidate.name?.trim() || `agent token #${candidate.agentId.toString()}`
96
+ return candidate.name?.trim() || `Agent Token #${candidate.agentId.toString()}`
97
97
  }
98
98
 
99
99
  export function tokenCandidateSelectLabel(
@@ -200,8 +200,8 @@ export function identitySummaryRows(
200
200
  const tokenValue = identity?.agentId ? `#${identity.agentId}` : 'not created'
201
201
  const chain = chainSummaryRow(config, identity)
202
202
  const stateValue = backup?.cid ? shortCid(backup.cid) : 'not saved yet'
203
- const skillsValue = identity?.publicSkills?.cid ? shortCid(identity.publicSkills.cid) : 'not published'
204
- const cardValue = identity?.publicSkills?.agentCardCid ? shortCid(identity.publicSkills.agentCardCid) : 'not published'
203
+ const skillsValue = identity?.publicSkills?.cid ? shortCid(identity.publicSkills.cid) : 'not saved'
204
+ const cardValue = identity?.publicSkills?.agentCardCid ? shortCid(identity.publicSkills.agentCardCid) : 'not saved'
205
205
  const imageValue = typeof identity?.state?.imageUrl === 'string' && identity.state.imageUrl.trim() ? 'attached' : 'not attached'
206
206
  return [
207
207
  { label: 'owner', value: ownerValue, tone: identity ? 'ok' : 'dim' },
@@ -232,8 +232,8 @@ export function identityDetailSections(
232
232
  const chain = chainSummaryRow(config, identity)
233
233
  const stateCid = backup?.cid ?? 'not saved yet'
234
234
  const registrationCid = identity?.metadataCid ?? 'not saved yet'
235
- const publicSkillsCid = identity?.publicSkills?.cid ?? 'not published'
236
- const agentCardCid = identity?.publicSkills?.agentCardCid ?? 'not published'
235
+ const publicSkillsCid = identity?.publicSkills?.cid ?? 'not saved'
236
+ const agentCardCid = identity?.publicSkills?.agentCardCid ?? 'not saved'
237
237
 
238
238
  return [
239
239
  {
@@ -273,14 +273,14 @@ export type CopyableField = {
273
273
  export function copyableIdentityFields(identity?: EthagentIdentity): CopyableField[] {
274
274
  if (!identity) return []
275
275
  const fields: CopyableField[] = []
276
- if (identity.backup?.cid) fields.push({ label: 'state CID', value: identity.backup.cid })
277
- if (identity.publicSkills?.cid) fields.push({ label: 'skills CID', value: identity.publicSkills.cid })
278
- if (identity.publicSkills?.agentCardCid) fields.push({ label: 'agent card CID', value: identity.publicSkills.agentCardCid })
279
- if (identity.metadataCid) fields.push({ label: 'registration CID', value: identity.metadataCid })
280
- if (identity.agentUri) fields.push({ label: 'agent URI', value: identity.agentUri })
276
+ if (identity.backup?.cid) fields.push({ label: 'State CID', value: identity.backup.cid })
277
+ if (identity.publicSkills?.cid) fields.push({ label: 'Skills CID', value: identity.publicSkills.cid })
278
+ if (identity.publicSkills?.agentCardCid) fields.push({ label: 'Agent Card CID', value: identity.publicSkills.agentCardCid })
279
+ if (identity.metadataCid) fields.push({ label: 'Registration CID', value: identity.metadataCid })
280
+ if (identity.agentUri) fields.push({ label: 'Agent URI', value: identity.agentUri })
281
281
  const owner = identity.ownerAddress ?? identity.address
282
- if (owner) fields.push({ label: 'owner address', value: owner })
283
- if (identity.agentId) fields.push({ label: 'token id', value: identity.agentId })
282
+ if (owner) fields.push({ label: 'Owner Address', value: owner })
283
+ if (identity.agentId) fields.push({ label: 'Token ID', value: identity.agentId })
284
284
  return fields
285
285
  }
286
286