ethagent 2.4.0 → 3.0.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 (98) 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 +125 -0
  23. package/src/identity/continuity/publicSkills.ts +37 -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 +105 -3
  37. package/src/identity/hub/Routes.tsx +5 -3
  38. package/src/identity/hub/continuity/ContinuityDashboardScreen.tsx +5 -51
  39. package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +1 -1
  40. package/src/identity/hub/continuity/SavePromptScreen.tsx +1 -0
  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/DeleteSkillScreen.tsx +123 -0
  44. package/src/identity/hub/continuity/skills/NewSkillScreen.tsx +57 -0
  45. package/src/identity/hub/continuity/skills/NewSkillVisibilityScreen.tsx +52 -0
  46. package/src/identity/hub/continuity/skills/SkillVisibilityScreen.tsx +171 -0
  47. package/src/identity/hub/continuity/skills/SkillsTreeScreen.tsx +213 -0
  48. package/src/identity/hub/continuity/snapshot.ts +3 -0
  49. package/src/identity/hub/continuity/state.ts +3 -2
  50. package/src/identity/hub/continuity/vault.ts +42 -10
  51. package/src/identity/hub/custody/CustodyEditFlow.tsx +3 -3
  52. package/src/identity/hub/identityHubReducer.ts +21 -0
  53. package/src/identity/hub/profile/effects.ts +16 -3
  54. package/src/identity/hub/restore/RestoreFlow.tsx +43 -6
  55. package/src/identity/hub/restore/apply.ts +12 -1
  56. package/src/identity/hub/restore/recovery.ts +11 -1
  57. package/src/identity/hub/restore/resolve.ts +1 -1
  58. package/src/identity/hub/restore/useRestoreEffects.ts +4 -6
  59. package/src/identity/hub/shared/components/DetailsScreen.tsx +4 -1
  60. package/src/identity/hub/shared/components/IdentitySummary.tsx +97 -53
  61. package/src/identity/hub/shared/components/MenuScreen.tsx +18 -15
  62. package/src/identity/hub/shared/components/UnlinkedIdentityScreen.tsx +1 -1
  63. package/src/identity/hub/shared/components/menuFlagsFromReconciliation.ts +8 -12
  64. package/src/identity/hub/shared/effects/sync.ts +16 -3
  65. package/src/identity/hub/shared/model/copy.ts +2 -4
  66. package/src/identity/hub/transfer/effects.ts +15 -2
  67. package/src/identity/hub/useIdentityHubContinuity.ts +145 -23
  68. package/src/identity/hub/useIdentityHubController.ts +5 -1
  69. package/src/identity/hub/useIdentityHubSideEffects.ts +2 -4
  70. package/src/mcp/manager.ts +1 -1
  71. package/src/models/ModelPicker.tsx +89 -84
  72. package/src/models/llamacpp.ts +160 -11
  73. package/src/models/llamacppPreflight.ts +1 -16
  74. package/src/models/modelPickerOptions.ts +43 -37
  75. package/src/providers/contracts.ts +1 -0
  76. package/src/providers/openai-chat.ts +50 -9
  77. package/src/providers/openai-responses.ts +19 -4
  78. package/src/runtime/toolExecution.ts +4 -3
  79. package/src/runtime/turn.ts +61 -30
  80. package/src/tools/changeDirectoryTool.ts +1 -1
  81. package/src/tools/contracts.ts +10 -0
  82. package/src/tools/deleteFileTool.ts +1 -1
  83. package/src/tools/editTool.ts +1 -1
  84. package/src/tools/listDirectoryTool.ts +1 -1
  85. package/src/tools/listSkillFilesTool.ts +77 -0
  86. package/src/tools/listSkillsTool.ts +68 -0
  87. package/src/tools/mcpResourceTools.ts +2 -2
  88. package/src/tools/privateContinuityReadTool.ts +1 -1
  89. package/src/tools/readSkillTool.ts +107 -0
  90. package/src/tools/readTool.ts +1 -1
  91. package/src/tools/registry.ts +6 -0
  92. package/src/tools/writeFileTool.ts +22 -2
  93. package/src/ui/Spinner.tsx +1 -1
  94. package/src/identity/continuity/localBackup.ts +0 -249
  95. package/src/identity/continuity/zipWriter.ts +0 -95
  96. package/src/identity/hub/continuity/index.ts +0 -7
  97. package/src/identity/hub/ens/index.ts +0 -11
  98. package/src/identity/hub/restore/index.ts +0 -22
@@ -2,15 +2,23 @@ import { useEffect, useState } from 'react'
2
2
  import type { EthagentIdentity } from '../../storage/config.js'
3
3
  import { catFromIpfs, DEFAULT_IPFS_API_URL } from '../storage/ipfs.js'
4
4
  import {
5
- continuityVaultRef,
6
5
  continuityVaultStatus,
7
6
  continuityWorkingTreeStatus,
8
7
  ensurePublicSkillsFile,
9
8
  type ContinuityWorkingTreeStatus,
10
9
  } from '../continuity/storage.js'
11
- import { openFileInEditor } from '../continuity/editor.js'
12
- import { exportLocalBackup } from '../continuity/localBackup.js'
10
+ import { openFileInEditor, openInFileManager } from '../continuity/editor.js'
13
11
  import { listPublishedContinuitySnapshots } from '../continuity/snapshots.js'
12
+ import {
13
+ createSkillFile,
14
+ deleteSkillEntry,
15
+ invalidateSkillsCache,
16
+ readSkillByRelativePath,
17
+ setSkillVisibility as setSkillVisibilityStorage,
18
+ } from '../continuity/skills/loadSkills.js'
19
+ import type { SkillVisibility } from '../continuity/skills/types.js'
20
+ import { syncPublicSkillsManifest } from '../continuity/skills/publicSkillsSync.js'
21
+ import { continuityVaultRef } from '../continuity/storage.js'
14
22
  import type { Step } from './identityHubReducer.js'
15
23
 
16
24
  type UseIdentityHubContinuityArgs = {
@@ -30,7 +38,11 @@ export function useIdentityHubContinuity({
30
38
  setContinuityReady: (ready: boolean) => void
31
39
  workingStatus: ContinuityWorkingTreeStatus | null
32
40
  openContinuityFile: (kind: 'soul' | 'memory' | 'skills') => Promise<void>
33
- exportLocalBackupZip: () => Promise<void>
41
+ openSkillFile: (relativePath: string) => Promise<void>
42
+ openSkillsFolder: () => Promise<void>
43
+ createSkill: (name: string, visibility: SkillVisibility) => Promise<void>
44
+ deleteSkill: (relativePath: string) => Promise<void>
45
+ setSkillVisibility: (relativePath: string, visibility: SkillVisibility) => Promise<void>
34
46
  } {
35
47
  const [continuityReady, setContinuityReady] = useState<boolean>(false)
36
48
  const [workingStatus, setWorkingStatus] = useState<ContinuityWorkingTreeStatus | null>(null)
@@ -55,6 +67,7 @@ export function useIdentityHubContinuity({
55
67
  step.kind !== 'menu'
56
68
  && step.kind !== 'continuity-private'
57
69
  && step.kind !== 'continuity-public'
70
+ && step.kind !== 'continuity-skills-tree'
58
71
  && step.kind !== 'save-prompt'
59
72
  && step.kind !== 'rebackup-confirm'
60
73
  ) return
@@ -78,8 +91,39 @@ export function useIdentityHubContinuity({
78
91
  }
79
92
  }, [identity, step.kind])
80
93
 
94
+ const requireReadyVault = async (): Promise<EthagentIdentity> => {
95
+ if (!identity) throw new Error('No active identity')
96
+ const status = await continuityVaultStatus(identity)
97
+ if (!status.ready) {
98
+ throw new Error('Restore local continuity files before editing the skills tree')
99
+ }
100
+ return identity
101
+ }
102
+
103
+ const mutateSkillsTree = async (args: {
104
+ backStep: Step
105
+ run: (id: EthagentIdentity) => Promise<string>
106
+ successStep?: (notice: string) => Step
107
+ }): Promise<void> => {
108
+ try {
109
+ const id = await requireReadyVault()
110
+ const notice = await args.run(id)
111
+ invalidateSkillsCache(id)
112
+ await syncPublicSkillsManifest(id)
113
+ const next = args.successStep
114
+ ? args.successStep(notice)
115
+ : { kind: 'continuity-skills-tree' as const, notice }
116
+ setStep(next)
117
+ } catch (err: unknown) {
118
+ handleStepError(err, args.backStep)
119
+ }
120
+ }
121
+
81
122
  const openContinuityFile = async (kind: 'soul' | 'memory' | 'skills'): Promise<void> => {
82
123
  if (!identity) return
124
+ const returnKind: 'continuity-private' | 'continuity-skills-tree' = kind === 'skills'
125
+ ? 'continuity-skills-tree'
126
+ : 'continuity-private'
83
127
  try {
84
128
  if (kind === 'skills') {
85
129
  await ensurePublicSkillsFile(identity, {
@@ -89,43 +133,121 @@ export function useIdentityHubContinuity({
89
133
  const ref = continuityVaultRef(identity)
90
134
  const file = kind === 'soul' ? ref.soulPath : kind === 'memory' ? ref.memoryPath : ref.publicSkillsPath
91
135
  const result = await openFileInEditor(file)
92
- const displayName = kind === 'soul' ? 'SOUL.md' : kind === 'memory' ? 'MEMORY.md' : 'skills.json'
93
- const message = result.ok
94
- ? `opened ${displayName} with ${result.method}.`
95
- : `open failed: ${result.error}`
96
- setStep({ kind: 'continuity-private', notice: message, editorOpened: result.ok })
136
+ if (result.ok) {
137
+ setStep({ kind: returnKind, editorOpened: true })
138
+ } else {
139
+ setStep({ kind: returnKind, notice: `open failed: ${result.error}`, editorOpened: false })
140
+ }
97
141
  } catch (err: unknown) {
98
- handleStepError(err, { kind: 'continuity-private' })
142
+ handleStepError(err, { kind: returnKind })
99
143
  }
100
144
  }
101
145
 
102
- const exportLocalBackupZip = async (): Promise<void> => {
146
+ const openSkillFile = async (relativePath: string): Promise<void> => {
103
147
  if (!identity) return
104
148
  try {
105
- await ensurePublicSkillsFile(identity, {
106
- fallback: () => readPublishedPublicSkills(identity),
107
- })
108
- const result = await exportLocalBackup(identity)
109
- const message = result.ok
110
- ? `Saved local backup to ${result.path}`
111
- : result.cancelled
112
- ? 'Backup cancelled'
113
- : `Backup failed: ${result.error}`
114
- setStep({ kind: 'continuity-private', notice: message })
149
+ const skill = await readSkillByRelativePath(identity, relativePath)
150
+ const result = await openFileInEditor(skill.absolutePath)
151
+ invalidateSkillsCache(identity)
152
+ try {
153
+ await syncPublicSkillsManifest(identity)
154
+ } catch (syncErr: unknown) {
155
+ const failPrefix = result.ok ? '' : `open failed: ${result.error}; `
156
+ setStep({ kind: 'continuity-skills-tree', notice: `${failPrefix}public manifest sync failed: ${(syncErr as Error).message}`, editorOpened: result.ok })
157
+ return
158
+ }
159
+ if (result.ok) {
160
+ setStep({ kind: 'continuity-skills-tree', editorOpened: true })
161
+ } else {
162
+ setStep({ kind: 'continuity-skills-tree', notice: `open failed: ${result.error}`, editorOpened: false })
163
+ }
115
164
  } catch (err: unknown) {
116
- handleStepError(err, { kind: 'continuity-private' })
165
+ handleStepError(err, { kind: 'continuity-skills-tree' })
117
166
  }
118
167
  }
119
168
 
169
+ const openSkillsFolder = async (): Promise<void> => {
170
+ if (!identity) return
171
+ try {
172
+ const ref = continuityVaultRef(identity)
173
+ const result = await openInFileManager(ref.skillsDir)
174
+ if (result.ok) {
175
+ setStep({ kind: 'continuity-skills-tree', editorOpened: true })
176
+ } else {
177
+ setStep({ kind: 'continuity-skills-tree', notice: `open failed: ${result.error}`, editorOpened: false })
178
+ }
179
+ } catch (err: unknown) {
180
+ handleStepError(err, { kind: 'continuity-skills-tree' })
181
+ }
182
+ }
183
+
184
+ const createSkill = async (name: string, visibility: SkillVisibility): Promise<void> => {
185
+ const normalizedName = sanitizeSkillSegment(name)
186
+ if (!normalizedName) {
187
+ handleStepError(
188
+ new Error('Folder name must contain only letters, numbers, dashes, underscores, or dots'),
189
+ { kind: 'continuity-skill-new' },
190
+ )
191
+ return
192
+ }
193
+ try {
194
+ const id = await requireReadyVault()
195
+ const created = await createSkillFile(id, { name: normalizedName, visibility })
196
+ invalidateSkillsCache(id)
197
+ await syncPublicSkillsManifest(id)
198
+ const result = await openFileInEditor(created.absolutePath)
199
+ if (result.ok) {
200
+ setStep({ kind: 'continuity-skills-tree', editorOpened: true })
201
+ } else {
202
+ setStep({ kind: 'continuity-skills-tree', notice: `created ${created.relativePath}; open failed: ${result.error}`, editorOpened: false })
203
+ }
204
+ } catch (err: unknown) {
205
+ handleStepError(err, { kind: 'continuity-skill-new' })
206
+ }
207
+ }
208
+
209
+ const deleteSkill = async (relativePath: string): Promise<void> => {
210
+ await mutateSkillsTree({
211
+ backStep: { kind: 'continuity-skill-delete' },
212
+ run: async id => {
213
+ await deleteSkillEntry(id, relativePath)
214
+ return `deleted ${relativePath}`
215
+ },
216
+ })
217
+ }
218
+
219
+ const setSkillVisibility = async (
220
+ relativePath: string,
221
+ visibility: SkillVisibility,
222
+ ): Promise<void> => {
223
+ await mutateSkillsTree({
224
+ backStep: { kind: 'continuity-skill-visibility' },
225
+ successStep: notice => ({ kind: 'continuity-skill-visibility', notice }),
226
+ run: async id => {
227
+ await setSkillVisibilityStorage(id, relativePath, visibility)
228
+ const display = relativePath.split('/')[0] ?? relativePath
229
+ return `${display} now ${visibility}`
230
+ },
231
+ })
232
+ }
233
+
120
234
  return {
121
235
  continuityReady,
122
236
  setContinuityReady,
123
237
  workingStatus,
124
238
  openContinuityFile,
125
- exportLocalBackupZip,
239
+ openSkillFile,
240
+ openSkillsFolder,
241
+ createSkill,
242
+ deleteSkill,
243
+ setSkillVisibility,
126
244
  }
127
245
  }
128
246
 
247
+ export function sanitizeSkillSegment(value: string): string {
248
+ return value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 60)
249
+ }
250
+
129
251
  async function readPublishedPublicSkills(identity: EthagentIdentity): Promise<string> {
130
252
  const cid = identity.publicSkills?.cid
131
253
  if (!cid) throw new Error('No saved public skills CID')
@@ -389,7 +389,11 @@ export function useIdentityHubController({
389
389
  openTokenTransferFlow,
390
390
  openPublicProfileEdit,
391
391
  openContinuityFile: continuity.openContinuityFile,
392
- exportLocalBackupZip: continuity.exportLocalBackupZip,
392
+ openSkillFile: continuity.openSkillFile,
393
+ openSkillsFolder: continuity.openSkillsFolder,
394
+ createSkill: continuity.createSkill,
395
+ deleteSkill: continuity.deleteSkill,
396
+ setSkillVisibility: continuity.setSkillVisibility,
393
397
  }
394
398
  }
395
399
 
@@ -20,10 +20,8 @@ import {
20
20
  runEnsSetupRecordsTransaction,
21
21
  runEnsSetupRegistryTransaction,
22
22
  runUpdateEnsRecords,
23
- } from './ens/index.js'
24
- import {
25
- runRecoveryRefetch,
26
- } from './restore/index.js'
23
+ } from './ens/transactions.js'
24
+ import { runRecoveryRefetch } from './restore/recovery.js'
27
25
  import type { EffectCallbacks } from './shared/effects/types.js'
28
26
  import { useRestoreEffects } from './restore/useRestoreEffects.js'
29
27
  import {
@@ -441,7 +441,7 @@ export class McpManager implements McpRuntime {
441
441
  async buildPermissionRequest() {
442
442
  return {
443
443
  kind: 'mcp',
444
- title: 'allow MCP tool?',
444
+ title: 'Allow MCP tool?',
445
445
  subtitle: `${connection.name} / ${tool.name}`,
446
446
  serverName: connection.name,
447
447
  normalizedServerName: connection.normalizedName,