ethagent 3.0.1 → 3.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ethagent",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "A privacy-first AI agent with a portable Ethereum identity",
5
5
  "type": "module",
6
6
  "main": "bin/ethagent.js",
@@ -23,6 +23,7 @@ import type { ModelPickerSelection } from '../models/ModelPicker.js'
23
23
  import type { ModelPickerContextFit } from '../models/modelPickerOptions.js'
24
24
  import type { CopyResult } from '../utils/clipboard.js'
25
25
  import { useKeybinding, useRegisterKeybindingContext } from '../app/keybindings/KeybindingProvider.js'
26
+ import { TITLE_ANIMATION_FRAMES, TITLE_ANIMATION_INTERVAL_MS, TITLE_STATIC, setTerminalTitle } from '../ui/terminalTitle.js'
26
27
  import { useCancelRequest } from '../app/hooks/useCancelRequest.js'
27
28
  import { useExitOnCtrlC } from '../app/hooks/useExitOnCtrlC.js'
28
29
  import {
@@ -1475,7 +1476,22 @@ export const ChatScreen: React.FC<ChatScreenProps> = ({ config: initialConfig, o
1475
1476
  ],
1476
1477
  )
1477
1478
 
1478
- const busy = pullInFlight || Boolean(compactionUi)
1479
+ const busy = streaming || pullInFlight || Boolean(compactionUi)
1480
+
1481
+ useEffect(() => {
1482
+ if (!busy) {
1483
+ setTerminalTitle(TITLE_STATIC)
1484
+ return
1485
+ }
1486
+ let i = 0
1487
+ setTerminalTitle(TITLE_ANIMATION_FRAMES[0])
1488
+ const id = setInterval(() => {
1489
+ i = (i + 1) % TITLE_ANIMATION_FRAMES.length
1490
+ setTerminalTitle(TITLE_ANIMATION_FRAMES[i] ?? TITLE_STATIC)
1491
+ }, TITLE_ANIMATION_INTERVAL_MS)
1492
+ return () => clearInterval(id)
1493
+ }, [busy])
1494
+
1479
1495
  const slashSuggestions = useMemo(
1480
1496
  () => getSlashSuggestions(mcpManagerRef.current?.getPromptSuggestions() ?? []),
1481
1497
  [mcpSnapshot],
package/src/cli/main.tsx CHANGED
@@ -14,6 +14,7 @@ import { runResetCommand } from './reset.js'
14
14
  import { runPreviewCommand } from './preview.js'
15
15
  import { checkForUpdates } from './updateNotice.js'
16
16
  import { Spinner } from '../ui/Spinner.js'
17
+ import { TITLE_STATIC, clearTerminalTitle, setTerminalTitle } from '../ui/terminalTitle.js'
17
18
 
18
19
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
19
20
 
@@ -87,6 +88,10 @@ const AppRoot: React.FC<{ setExitCode: (code: number) => void; currentVersion: s
87
88
  return () => { cancelled = true }
88
89
  }, [currentVersion])
89
90
 
91
+ useEffect(() => {
92
+ setTerminalTitle(TITLE_STATIC)
93
+ }, [])
94
+
90
95
  useEffect(() => {
91
96
  if (phase.kind === 'cancelled') {
92
97
  setExitCode(1)
@@ -152,6 +157,8 @@ const AppRoot: React.FC<{ setExitCode: (code: number) => void; currentVersion: s
152
157
 
153
158
  async function runDefault(currentVersion: string): Promise<number> {
154
159
  let exitCode = 0
160
+ setTerminalTitle(TITLE_STATIC)
161
+ process.once('exit', clearTerminalTitle)
155
162
  const instance = render(
156
163
  <AppInputProvider>
157
164
  <KeybindingProvider>
@@ -15,7 +15,7 @@ type PublicSkillsProfile = {
15
15
  name: string
16
16
  description: string
17
17
  version: string
18
- agentWallet: string
18
+ agentWallet?: string
19
19
  imageUrl?: string
20
20
  skills: PublicSkill[]
21
21
  }
@@ -34,7 +34,7 @@ type AgentCard = {
34
34
  pushNotifications: boolean
35
35
  }
36
36
  producer: { name: string; url: string }
37
- agent_wallet: string
37
+ agent_wallet?: string
38
38
  skills: Array<{
39
39
  id: string
40
40
  name: string
@@ -66,7 +66,7 @@ export function defaultPublicSkillsProfile(identity: EthagentIdentity): PublicSk
66
66
  name,
67
67
  description,
68
68
  version: '1.0.0',
69
- agentWallet,
69
+ ...(agentWallet ? { agentWallet } : {}),
70
70
  ...(imageUrl ? { imageUrl } : {}),
71
71
  skills: [
72
72
  {
@@ -135,7 +135,7 @@ export function renderPublicSkillsJson(profile: PublicSkillsProfile): string {
135
135
  const summary = {
136
136
  schema: 'ethagent.public-skills.v1',
137
137
  producer: ETHAGENT_PRODUCER,
138
- agent_wallet: profile.agentWallet,
138
+ ...(profile.agentWallet ? { agent_wallet: profile.agentWallet } : {}),
139
139
  visibility: 'public',
140
140
  name: profile.name,
141
141
  description: profile.description,
@@ -191,6 +191,7 @@ export function createAgentCard(profile: PublicSkillsProfile, url?: string): Age
191
191
  name: profile.name,
192
192
  description: profile.description,
193
193
  version: profile.version,
194
+ ...(profile.agentWallet ? { agent_wallet: profile.agentWallet } : {}),
194
195
  protocolVersion: '0.2.6',
195
196
  ...(url ? { url } : {}),
196
197
  ...(profile.imageUrl ? { image: profile.imageUrl } : {}),
@@ -201,7 +202,6 @@ export function createAgentCard(profile: PublicSkillsProfile, url?: string): Age
201
202
  pushNotifications: false,
202
203
  },
203
204
  producer: ETHAGENT_PRODUCER,
204
- agent_wallet: profile.agentWallet,
205
205
  skills: profile.skills.map(skill => ({
206
206
  id: skill.id,
207
207
  name: skill.name,
@@ -181,16 +181,15 @@ function serializeOperatorsPointer(pointer: EthagentOperatorsPointer): Record<st
181
181
  export function withEthagentBackupPointer(
182
182
  registration: Record<string, unknown> | null,
183
183
  backup: EthagentBackupPointer,
184
- publicDiscovery?: EthagentPublicDiscoveryPointer,
185
- registrationPointer?: EthagentRegistrationPointer,
186
- ownerAddress?: Address,
184
+ publicDiscovery: EthagentPublicDiscoveryPointer | undefined,
185
+ registrationPointer: EthagentRegistrationPointer | undefined,
186
+ ownerAddress: Address,
187
187
  ): Record<string, unknown> {
188
- const inferredOwnerAddress = ownerAddress ?? backup.agentAddress
189
188
  return withEthagentPointers(registration, {
190
189
  backup,
191
190
  publicDiscovery,
192
191
  registration: registrationPointer,
193
- ...(inferredOwnerAddress ? { ownerAddress: inferredOwnerAddress } : {}),
192
+ ownerAddress,
194
193
  })
195
194
  }
196
195
 
@@ -202,29 +201,26 @@ export function withEthagentPointers(
202
201
  registration?: EthagentRegistrationPointer
203
202
  ensName?: string
204
203
  operators?: EthagentOperatorsPointer
205
- ownerAddress?: Address
204
+ ownerAddress: Address
206
205
  },
207
206
  ): Record<string, unknown> {
208
207
  const next: Record<string, unknown> = registration ? { ...registration } : {}
209
208
  const prior = objectField(next, 'x-ethagent') ?? {}
210
209
  const { backup, publicDiscovery, registration: registrationPointer, operators } = pointers
211
210
  const updatedAt = publicDiscovery?.updatedAt ?? backup?.createdAt
212
- const ownerAddress = pointers.ownerAddress
213
- ? getAddress(pointers.ownerAddress)
214
- : backup?.agentAddress
215
- ? getAddress(backup.agentAddress)
216
- : undefined
211
+ if (!pointers.ownerAddress) {
212
+ throw new Error('withEthagentPointers requires ownerAddress')
213
+ }
214
+ const ownerAddress = getAddress(pointers.ownerAddress)
217
215
  const priorX402 = objectField(prior, 'x402') ?? {}
218
216
  const ext: Record<string, unknown> = {
219
217
  ...prior,
220
218
  version: 1,
221
- ...(ownerAddress ? {
222
- agentAddress: ownerAddress,
223
- x402: {
224
- ...priorX402,
225
- walletAddress: ownerAddress,
226
- },
227
- } : {}),
219
+ agentAddress: ownerAddress,
220
+ x402: {
221
+ ...priorX402,
222
+ walletAddress: ownerAddress,
223
+ },
228
224
  ...(backup ? {
229
225
  backup: {
230
226
  cid: backup.cid,
@@ -252,8 +248,11 @@ export function withEthagentPointers(
252
248
  delete ext.transfer
253
249
  delete ext.handoff
254
250
  next['x-ethagent'] = ext
255
- if (publicDiscovery) {
256
- next.services = withPublicDiscoveryServices(next.services, publicDiscovery, pointers.ensName)
251
+ const agentWalletService = registrationPointer
252
+ ? { name: 'agentWallet' as const, endpoint: `eip155:${registrationPointer.chainId}:${ownerAddress}` }
253
+ : undefined
254
+ if (publicDiscovery || agentWalletService) {
255
+ next.services = withEthagentServices(next.services, publicDiscovery, pointers.ensName, agentWalletService)
257
256
  }
258
257
  if (registrationPointer && registrationPointer.agentId !== undefined) {
259
258
  next.registrations = withRegistrationsArray(next.registrations, registrationPointer)
@@ -272,10 +271,18 @@ function serializeTransferSnapshotMetadata(metadata: TransferSnapshotMetadata):
272
271
  }
273
272
  }
274
273
 
275
- function withPublicDiscoveryServices(input: unknown, publicDiscovery: EthagentPublicDiscoveryPointer, ensName?: string): unknown[] {
274
+ function withEthagentServices(
275
+ input: unknown,
276
+ publicDiscovery: EthagentPublicDiscoveryPointer | undefined,
277
+ ensName: string | undefined,
278
+ agentWallet: { name: 'agentWallet'; endpoint: string } | undefined,
279
+ ): unknown[] {
276
280
  const prior = Array.isArray(input) ? input.filter(item => item && typeof item === 'object') : []
277
281
  const services = prior.filter(item => !isEthagentManagedService(item)) as unknown[]
278
- if (publicDiscovery.agentCardCid) {
282
+ if (agentWallet) {
283
+ pushUniqueService(services, agentWallet)
284
+ }
285
+ if (publicDiscovery?.agentCardCid) {
279
286
  const endpoint = `ipfs://${publicDiscovery.agentCardCid}`
280
287
  pushUniqueService(services, {
281
288
  type: 'a2a',
@@ -284,7 +291,7 @@ function withPublicDiscoveryServices(input: unknown, publicDiscovery: EthagentPu
284
291
  url: endpoint,
285
292
  })
286
293
  }
287
- if (publicDiscovery.skillsCid) {
294
+ if (publicDiscovery?.skillsCid) {
288
295
  const endpoint = `ipfs://${publicDiscovery.skillsCid}`
289
296
  pushUniqueService(services, {
290
297
  type: 'A2A-skills',
@@ -313,6 +320,7 @@ function isEthagentManagedService(item: unknown): boolean {
313
320
  const obj = item as Record<string, unknown>
314
321
  const type = obj.type
315
322
  const name = obj.name
323
+ if (name === 'agentWallet') return true
316
324
  if (name === 'ENS') return true
317
325
  if (type === 'a2a' && (name === undefined || name === 'agent-card')) return true
318
326
  return (type === 'A2A-skills' || type === 'ipfs') && name === 'public-skills'
@@ -0,0 +1,30 @@
1
+ export const TITLE_STATIC = 'ethagent'
2
+ export const TITLE_ANIMATION_FRAMES = [
3
+ 'ethagent ⠋',
4
+ 'ethagent ⠙',
5
+ 'ethagent ⠹',
6
+ 'ethagent ⠸',
7
+ 'ethagent ⠼',
8
+ 'ethagent ⠴',
9
+ 'ethagent ⠦',
10
+ 'ethagent ⠧',
11
+ 'ethagent ⠇',
12
+ 'ethagent ⠏',
13
+ ] as const
14
+ export const TITLE_ANIMATION_INTERVAL_MS = 80
15
+
16
+ export function setTerminalTitle(title: string): void {
17
+ const clean = title.replace(/[\x00-\x1f]/g, '')
18
+ if (process.platform === 'win32') {
19
+ process.title = clean
20
+ }
21
+ if (process.stdout.isTTY) {
22
+ process.stdout.write(`\x1b]0;${clean}\x07`)
23
+ }
24
+ }
25
+
26
+ export function clearTerminalTitle(): void {
27
+ if (process.stdout.isTTY) {
28
+ process.stdout.write('\x1b]0;\x07')
29
+ }
30
+ }