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
@@ -0,0 +1,183 @@
1
+ import type { SkillFrontmatter, SkillVisibility } from './types.js'
2
+
3
+ const SUPPORTED_KEYS = new Set([
4
+ 'name',
5
+ 'description',
6
+ 'when_to_use',
7
+ 'when-to-use',
8
+ 'whenToUse',
9
+ 'version',
10
+ 'argument-hint',
11
+ 'argument_hint',
12
+ 'argumentHint',
13
+ 'tags',
14
+ 'visibility',
15
+ ])
16
+
17
+ const VISIBILITY_VALUES: SkillVisibility[] = ['private', 'public', 'discoverable']
18
+
19
+ export type ParsedSkillFile = {
20
+ frontmatter: SkillFrontmatter
21
+ body: string
22
+ }
23
+
24
+ export function parseSkillFile(content: string): ParsedSkillFile {
25
+ const normalized = content.replace(/^/, '').replace(/\r\n?/g, '\n')
26
+ if (!normalized.startsWith('---\n') && normalized !== '---' && !normalized.startsWith('---\r')) {
27
+ return { frontmatter: {}, body: normalized.trim() }
28
+ }
29
+ const afterOpen = normalized.slice(4)
30
+ const closeIdx = afterOpen.search(/^---\s*$/m)
31
+ if (closeIdx < 0) {
32
+ return { frontmatter: {}, body: normalized.trim() }
33
+ }
34
+ const fmText = afterOpen.slice(0, closeIdx)
35
+ const bodyText = afterOpen.slice(closeIdx).replace(/^---\s*\n?/, '').replace(/^\n+/, '')
36
+ return {
37
+ frontmatter: parseFrontmatterBlock(fmText),
38
+ body: bodyText.trim(),
39
+ }
40
+ }
41
+
42
+ function parseFrontmatterBlock(text: string): SkillFrontmatter {
43
+ const out: SkillFrontmatter = {}
44
+ const lines = text.split('\n')
45
+ for (let i = 0; i < lines.length; i++) {
46
+ const line = lines[i]
47
+ if (line === undefined) continue
48
+ const trimmed = line.trim()
49
+ if (!trimmed || trimmed.startsWith('#')) continue
50
+ const match = /^([A-Za-z_][A-Za-z0-9_\-]*)\s*:\s*(.*)$/.exec(line)
51
+ if (!match) continue
52
+ const rawKey = match[1] ?? ''
53
+ if (!SUPPORTED_KEYS.has(rawKey)) continue
54
+ let rawValue = match[2] ?? ''
55
+ if (rawValue === '' || rawValue === '|' || rawValue === '>') {
56
+ const collected: string[] = []
57
+ while (i + 1 < lines.length) {
58
+ const next = lines[i + 1]
59
+ if (next === undefined) break
60
+ if (next.startsWith(' ') || next.startsWith('\t')) {
61
+ collected.push(next.replace(/^\s+/, ''))
62
+ i++
63
+ } else if (next === '' || next.trim() === '') {
64
+ break
65
+ } else {
66
+ break
67
+ }
68
+ }
69
+ rawValue = collected.join(' ').trim()
70
+ }
71
+ const key = normalizeKey(rawKey)
72
+ if (!key) continue
73
+ assignKey(out, key, rawValue)
74
+ }
75
+ return out
76
+ }
77
+
78
+ function normalizeKey(key: string): keyof SkillFrontmatter | null {
79
+ switch (key) {
80
+ case 'name': return 'name'
81
+ case 'description': return 'description'
82
+ case 'when_to_use':
83
+ case 'when-to-use':
84
+ case 'whenToUse':
85
+ return 'whenToUse'
86
+ case 'version': return 'version'
87
+ case 'argument-hint':
88
+ case 'argument_hint':
89
+ case 'argumentHint':
90
+ return 'argumentHint'
91
+ case 'tags': return 'tags'
92
+ case 'visibility': return 'visibility'
93
+ default: return null
94
+ }
95
+ }
96
+
97
+ function assignKey(out: SkillFrontmatter, key: keyof SkillFrontmatter, rawValue: string): void {
98
+ const stripped = stripInlineComment(rawValue).trim()
99
+ if (key === 'tags') {
100
+ out.tags = parseStringList(stripped)
101
+ return
102
+ }
103
+ if (key === 'visibility') {
104
+ const literal = parseScalar(stripped).toLowerCase()
105
+ if ((VISIBILITY_VALUES as string[]).includes(literal)) {
106
+ out.visibility = literal as SkillVisibility
107
+ }
108
+ return
109
+ }
110
+ const value = parseScalar(stripped)
111
+ if (!value) return
112
+ out[key] = value
113
+ }
114
+
115
+ function parseScalar(value: string): string {
116
+ if (value === '') return ''
117
+ if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
118
+ return unescapeDoubleQuoted(value.slice(1, -1))
119
+ }
120
+ if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
121
+ return value.slice(1, -1).replace(/''/g, "'")
122
+ }
123
+ return value
124
+ }
125
+
126
+ function parseStringList(value: string): string[] {
127
+ if (!value) return []
128
+ if (value.startsWith('[') && value.endsWith(']')) {
129
+ const inner = value.slice(1, -1)
130
+ return splitListItems(inner)
131
+ .map(parseScalar)
132
+ .filter(item => item.length > 0)
133
+ }
134
+ return splitListItems(value)
135
+ .map(parseScalar)
136
+ .filter(item => item.length > 0)
137
+ }
138
+
139
+ function splitListItems(value: string): string[] {
140
+ const items: string[] = []
141
+ let buffer = ''
142
+ let quote: string | null = null
143
+ for (let i = 0; i < value.length; i++) {
144
+ const ch = value[i]
145
+ if (quote) {
146
+ buffer += ch
147
+ if (ch === quote && value[i - 1] !== '\\') quote = null
148
+ continue
149
+ }
150
+ if (ch === '"' || ch === "'") {
151
+ quote = ch
152
+ buffer += ch
153
+ continue
154
+ }
155
+ if (ch === ',') {
156
+ items.push(buffer.trim())
157
+ buffer = ''
158
+ continue
159
+ }
160
+ buffer += ch
161
+ }
162
+ if (buffer.trim()) items.push(buffer.trim())
163
+ return items
164
+ }
165
+
166
+ function unescapeDoubleQuoted(value: string): string {
167
+ return value.replace(/\\(["\\/bfnrt])/g, (_, c) => {
168
+ switch (c) {
169
+ case 'n': return '\n'
170
+ case 'r': return '\r'
171
+ case 't': return '\t'
172
+ case 'b': return '\b'
173
+ case 'f': return '\f'
174
+ default: return c
175
+ }
176
+ })
177
+ }
178
+
179
+ function stripInlineComment(value: string): string {
180
+ if (value.startsWith('"') || value.startsWith("'") || value.startsWith('[')) return value
181
+ const hash = value.indexOf(' #')
182
+ return hash === -1 ? value : value.slice(0, hash)
183
+ }