ethagent 3.0.2 → 3.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.
- package/README.md +6 -1
- package/package.json +3 -1
- package/src/app/FirstRun.tsx +1 -24
- package/src/app/firstRunConfig.ts +26 -0
- package/src/auth/openaiOAuth/landingPage.ts +2 -11
- package/src/chat/ChatScreen.tsx +15 -116
- package/src/chat/MessageList.tsx +18 -260
- package/src/chat/chatEnvironment.ts +16 -0
- package/src/chat/chatTurnContext.ts +50 -0
- package/src/chat/chatTurnOrchestrator.ts +5 -112
- package/src/chat/chatTurnRows.ts +64 -0
- package/src/chat/commands.ts +3 -178
- package/src/chat/continuityEditReview.ts +42 -0
- package/src/chat/input/ChatInput.tsx +10 -144
- package/src/chat/input/chatInputHelpers.ts +62 -0
- package/src/chat/input/inputRendering.tsx +93 -0
- package/src/chat/messageMarkdown.ts +220 -0
- package/src/chat/messageRows.ts +43 -0
- package/src/chat/planImplementation.ts +62 -0
- package/src/chat/slashCommandHandlers.ts +165 -0
- package/src/chat/slashCommandViews.ts +120 -0
- package/src/identity/continuity/challenges.ts +123 -0
- package/src/identity/continuity/envelope.ts +49 -1484
- package/src/identity/continuity/envelopeCreate.ts +322 -0
- package/src/identity/continuity/envelopeCrypto.ts +182 -0
- package/src/identity/continuity/envelopeParse.ts +441 -0
- package/src/identity/continuity/envelopeTypes.ts +204 -0
- package/src/identity/continuity/envelopeVersion.ts +1 -0
- package/src/identity/continuity/payloadNormalization.ts +183 -0
- package/src/identity/continuity/skills/loadSkills.ts +12 -69
- package/src/identity/continuity/skills/skillPaths.ts +76 -0
- package/src/identity/continuity/skillsNormalization.ts +119 -0
- package/src/identity/continuity/snapshotToken.ts +28 -0
- package/src/identity/hub/continuity/completion.ts +67 -0
- package/src/identity/hub/continuity/effects.ts +5 -62
- package/src/identity/hub/profile/effects.ts +6 -170
- package/src/identity/hub/profile/operatorSave.ts +202 -0
- package/src/identity/wallet/browserWallet/html.ts +1 -57
- package/src/identity/wallet/browserWallet/walletPageSource.ts +85 -0
- package/src/identity/wallet/page/controller.ts +1 -1
- package/src/identity/wallet/page/errorView.ts +122 -0
- package/src/identity/wallet/page/view.ts +3 -114
- package/src/mcp/manager.ts +8 -66
- package/src/mcp/managerHelpers.ts +70 -0
- package/src/models/ModelPicker.tsx +69 -889
- package/src/models/huggingface.ts +20 -137
- package/src/models/huggingfaceStorage.ts +136 -0
- package/src/models/llamacpp.ts +37 -303
- package/src/models/llamacppCommands.ts +44 -0
- package/src/models/llamacppConfig.ts +34 -0
- package/src/models/llamacppDiscovery.ts +176 -0
- package/src/models/llamacppOutput.ts +65 -0
- package/src/models/modelPickerCatalogFlow.ts +56 -0
- package/src/models/modelPickerCredentials.ts +166 -0
- package/src/models/modelPickerData.ts +41 -0
- package/src/models/modelPickerDisplay.tsx +132 -0
- package/src/models/modelPickerHfFlow.ts +192 -0
- package/src/models/modelPickerLocalRunnerFlow.ts +115 -0
- package/src/models/modelPickerTypes.ts +69 -0
- package/src/models/modelPickerUninstallFlow.ts +48 -0
- package/src/models/modelPickerViewHelpers.ts +174 -0
- package/src/providers/openai-chat.ts +5 -124
- package/src/providers/openaiChatWire.ts +124 -0
- package/src/runtime/providerTurn.ts +38 -0
- package/src/runtime/textToolParser.ts +161 -0
- package/src/runtime/toolIntent.ts +1 -1
- package/src/runtime/turn.ts +43 -499
- package/src/runtime/turnNudges.ts +223 -0
- package/src/runtime/turnTypes.ts +86 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { EthagentConfig, ProviderId } from '../storage/config.js'
|
|
2
|
+
import { getConfigPath, localProviderBaseUrlFor } from '../storage/config.js'
|
|
3
|
+
import { detectLlamaCpp } from '../models/llamacpp.js'
|
|
4
|
+
import { detectSpec } from '../models/runtimeDetection.js'
|
|
5
|
+
import { getIdentityStatus } from '../storage/identity.js'
|
|
6
|
+
import { getLocalHfCacheDir } from '../models/huggingface.js'
|
|
7
|
+
import { formatModelDisplayName } from '../models/modelDisplay.js'
|
|
8
|
+
import { providerDisplayName } from '../models/modelPickerOptions.js'
|
|
9
|
+
import type { ModelCatalogResult } from '../models/catalog.js'
|
|
10
|
+
import type { SlashContext } from './commands.js'
|
|
11
|
+
|
|
12
|
+
export function renderStatus(ctx: SlashContext): string {
|
|
13
|
+
const elapsedMs = Date.now() - ctx.startedAt
|
|
14
|
+
const minutes = Math.floor(elapsedMs / 60000)
|
|
15
|
+
const seconds = Math.floor((elapsedMs % 60000) / 1000)
|
|
16
|
+
const elapsed = minutes > 0 ? `${minutes}m${seconds.toString().padStart(2, '0')}s` : `${seconds}s`
|
|
17
|
+
const displayModel = formatModelDisplayName(ctx.config.provider, ctx.config.model, { maxLength: 72 })
|
|
18
|
+
return [
|
|
19
|
+
`provider ${providerDisplayName(ctx.config.provider)}`,
|
|
20
|
+
`model ${displayModel}`,
|
|
21
|
+
`cwd ${ctx.cwd}`,
|
|
22
|
+
`session ${ctx.sessionId.slice(0, 8)}`,
|
|
23
|
+
'state active',
|
|
24
|
+
`turns ${ctx.turns}`,
|
|
25
|
+
`tokens ~${ctx.approxTokens}`,
|
|
26
|
+
`context ${ctx.contextUsage.percent}% (~${ctx.contextUsage.usedTokens}/${ctx.contextUsage.windowTokens}, ${ctx.contextUsage.source})`,
|
|
27
|
+
`elapsed ${elapsed}`,
|
|
28
|
+
].join('\n')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function renderContext(ctx: SlashContext): string {
|
|
32
|
+
const usage = ctx.contextUsage
|
|
33
|
+
const free = Math.max(0, usage.windowTokens - usage.usedTokens)
|
|
34
|
+
const action =
|
|
35
|
+
usage.percent >= 90
|
|
36
|
+
? 'Context is near the model limit. New requests will ask you to summarize into a new conversation, switch models, ignore and send, or cancel.'
|
|
37
|
+
: usage.percent >= 75
|
|
38
|
+
? 'Context is getting full. Consider /compact before a new task boundary.'
|
|
39
|
+
: 'Context has comfortable room.'
|
|
40
|
+
return [
|
|
41
|
+
'context usage:',
|
|
42
|
+
` model ${providerDisplayName(ctx.config.provider)} · ${formatModelDisplayName(ctx.config.provider, ctx.config.model, { maxLength: 72 })}`,
|
|
43
|
+
` used ~${usage.usedTokens} / ${usage.windowTokens} tokens (${usage.percent}%)`,
|
|
44
|
+
` free ~${free} tokens`,
|
|
45
|
+
` estimate ${usage.confidence} (${usage.source})`,
|
|
46
|
+
' session active',
|
|
47
|
+
'',
|
|
48
|
+
action,
|
|
49
|
+
].join('\n')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function renderDoctor(
|
|
53
|
+
spec: Awaited<ReturnType<typeof detectSpec>>,
|
|
54
|
+
keys: ReadonlyArray<readonly [ProviderId, boolean]>,
|
|
55
|
+
identity: Awaited<ReturnType<typeof getIdentityStatus>>,
|
|
56
|
+
ctx: SlashContext,
|
|
57
|
+
llamaCpp: Awaited<ReturnType<typeof detectLlamaCpp>>,
|
|
58
|
+
hfModelCount: number,
|
|
59
|
+
): string {
|
|
60
|
+
const lines: string[] = ['diagnostics:']
|
|
61
|
+
lines.push(` platform ${spec.platform}/${spec.arch}${spec.isAppleSilicon ? ' (apple silicon)' : ''}`)
|
|
62
|
+
lines.push(` ram ${formatGB(spec.effectiveRamBytes)}${spec.gpuVramBytes ? ` · vram ${formatGB(spec.gpuVramBytes)}` : ''}`)
|
|
63
|
+
lines.push(` local run ${llamaCpp.binaryPresent ? 'installed' : 'not installed'} · server ${llamaCpp.serverUp ? 'up' : 'down'}`)
|
|
64
|
+
lines.push(` hf models ${hfModelCount} downloaded`)
|
|
65
|
+
lines.push('')
|
|
66
|
+
lines.push('config:')
|
|
67
|
+
lines.push(` provider ${providerDisplayName(ctx.config.provider)}`)
|
|
68
|
+
lines.push(` model ${formatModelDisplayName(ctx.config.provider, ctx.config.model, { maxLength: 72 })}`)
|
|
69
|
+
if (ctx.config.baseUrl) lines.push(` baseUrl ${ctx.config.baseUrl}`)
|
|
70
|
+
if (ctx.config.provider === 'llamacpp') lines.push(` hf cache ${getLocalHfCacheDir()}`)
|
|
71
|
+
lines.push(` path ${getConfigPath()}`)
|
|
72
|
+
lines.push('')
|
|
73
|
+
lines.push('keys:')
|
|
74
|
+
for (const [provider, present] of keys) {
|
|
75
|
+
lines.push(` ${providerDisplayName(provider).padEnd(9)} ${present ? 'set' : 'not set'}`)
|
|
76
|
+
}
|
|
77
|
+
lines.push('')
|
|
78
|
+
lines.push('identity:')
|
|
79
|
+
if (identity) {
|
|
80
|
+
lines.push(` address ${identity.address}`)
|
|
81
|
+
lines.push(` backend ${identity.backend}`)
|
|
82
|
+
if (identity.source) lines.push(` source ${identity.source}`)
|
|
83
|
+
if (identity.agentId) lines.push(` token #${identity.agentId}`)
|
|
84
|
+
} else {
|
|
85
|
+
lines.push(' address not set')
|
|
86
|
+
}
|
|
87
|
+
return lines.join('\n')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function renderModelCatalog(catalog: ModelCatalogResult, currentModel: string): string {
|
|
91
|
+
const title = catalog.status === 'fallback'
|
|
92
|
+
? `${providerDisplayName(catalog.provider)} models (fallback${catalog.error ? `: ${catalog.error}` : ''}):`
|
|
93
|
+
: `${providerDisplayName(catalog.provider)} models:`
|
|
94
|
+
const lines = catalog.entries.map(entry => {
|
|
95
|
+
const marker = entry.id === currentModel ? '*' : ' '
|
|
96
|
+
const suffix = entry.source === 'fallback' ? ' fallback' : ''
|
|
97
|
+
return `${marker} ${formatModelDisplayName(catalog.provider, entry.id, { maxLength: 72 })}${suffix}`
|
|
98
|
+
})
|
|
99
|
+
return [title, ...lines].join('\n')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function baseUrlForModelSwitch(config: EthagentConfig): string | undefined {
|
|
103
|
+
if (config.provider === 'llamacpp') return localProviderBaseUrlFor('llamacpp', config.baseUrl)
|
|
104
|
+
if (config.provider === 'openai') return config.baseUrl
|
|
105
|
+
return undefined
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function formatBytes(bytes: number): string {
|
|
109
|
+
if (bytes <= 0) return '—'
|
|
110
|
+
const gb = bytes / (1024 * 1024 * 1024)
|
|
111
|
+
if (gb >= 1) return `${gb.toFixed(1)}GB`
|
|
112
|
+
const mb = bytes / (1024 * 1024)
|
|
113
|
+
return `${mb.toFixed(0)}MB`
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function formatGB(bytes: number): string {
|
|
117
|
+
const gb = bytes / (1024 * 1024 * 1024)
|
|
118
|
+
if (gb >= 10) return `${Math.round(gb)}GB`
|
|
119
|
+
return `${gb.toFixed(1)}GB`
|
|
120
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { toChecksumAddress } from '../crypto/eth.js'
|
|
2
|
+
import { normalizeContinuitySnapshotToken, type ContinuitySnapshotToken } from './snapshotToken.js'
|
|
3
|
+
|
|
4
|
+
const CONTINUITY_SNAPSHOT_CHALLENGE_MESSAGES = [
|
|
5
|
+
'Save or Restore Identity Files',
|
|
6
|
+
'Action: encrypt or decrypt local identity files',
|
|
7
|
+
'Private: SOUL.md, MEMORY.md, skills',
|
|
8
|
+
'Public: public skills and profile',
|
|
9
|
+
'Safety: no transaction, spending, or approvals',
|
|
10
|
+
'Version: 2',
|
|
11
|
+
] as const
|
|
12
|
+
|
|
13
|
+
export function createContinuitySnapshotChallenge(ownerAddress: string): string {
|
|
14
|
+
const checksum = toChecksumAddress(ownerAddress)
|
|
15
|
+
return [
|
|
16
|
+
CONTINUITY_SNAPSHOT_CHALLENGE_MESSAGES[0],
|
|
17
|
+
`Owner: ${checksum}`,
|
|
18
|
+
...CONTINUITY_SNAPSHOT_CHALLENGE_MESSAGES.slice(1),
|
|
19
|
+
].join('\n')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const TRANSFER_SNAPSHOT_CHALLENGE_HEADER_LEGACY = 'Prepare Transfer Restore Snapshot'
|
|
23
|
+
export const TRANSFER_SNAPSHOT_CHALLENGE_HEADER_SENDER = 'Prepare Transfer Snapshot · Sender Restore Slot'
|
|
24
|
+
export const TRANSFER_SNAPSHOT_CHALLENGE_HEADER_RECEIVER = 'Prepare Transfer Snapshot · Receiver Restore Slot'
|
|
25
|
+
|
|
26
|
+
export function createTransferContinuitySnapshotChallenge(args: {
|
|
27
|
+
token: ContinuitySnapshotToken
|
|
28
|
+
ownerAddress: string
|
|
29
|
+
targetAddress: string
|
|
30
|
+
role?: 'sender' | 'receiver'
|
|
31
|
+
}): string {
|
|
32
|
+
const token = normalizeContinuitySnapshotToken(args.token)
|
|
33
|
+
const ownerAddress = toChecksumAddress(args.ownerAddress)
|
|
34
|
+
const targetAddress = toChecksumAddress(args.targetAddress)
|
|
35
|
+
const header = args.role === 'sender'
|
|
36
|
+
? TRANSFER_SNAPSHOT_CHALLENGE_HEADER_SENDER
|
|
37
|
+
: args.role === 'receiver'
|
|
38
|
+
? TRANSFER_SNAPSHOT_CHALLENGE_HEADER_RECEIVER
|
|
39
|
+
: TRANSFER_SNAPSHOT_CHALLENGE_HEADER_LEGACY
|
|
40
|
+
return [
|
|
41
|
+
header,
|
|
42
|
+
`ERC-8004 Chain ID: ${token.chainId}`,
|
|
43
|
+
`ERC-8004 Registry: ${token.identityRegistryAddress}`,
|
|
44
|
+
`ERC-8004 Token ID: ${token.agentId}`,
|
|
45
|
+
`Sender Owner: ${ownerAddress}`,
|
|
46
|
+
`Receiver Owner: ${targetAddress}`,
|
|
47
|
+
'Action: encrypt or decrypt local identity files for this token transfer',
|
|
48
|
+
'Private: SOUL.md, MEMORY.md, skills',
|
|
49
|
+
'Public: public skills and profile',
|
|
50
|
+
'Safety: no transaction, spending, or approvals',
|
|
51
|
+
'Version: 2',
|
|
52
|
+
].join('\n')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type WalletChallengePurpose =
|
|
56
|
+
| 'create-agent'
|
|
57
|
+
| 'update-snapshot'
|
|
58
|
+
| 'update-ens-snapshot'
|
|
59
|
+
| 'clear-ens-snapshot'
|
|
60
|
+
| 'update-profile-snapshot'
|
|
61
|
+
| 'update-operators-snapshot'
|
|
62
|
+
| 'refetch-snapshot'
|
|
63
|
+
| 'operator-proof'
|
|
64
|
+
| 'restore-owner'
|
|
65
|
+
| 'restore-operator'
|
|
66
|
+
| 'transfer-prepare-sender'
|
|
67
|
+
| 'transfer-prepare-receiver'
|
|
68
|
+
|
|
69
|
+
const WALLET_CHALLENGE_V2_COPY: Record<WalletChallengePurpose, { title: string; action: string }> = {
|
|
70
|
+
'create-agent': { title: 'Create Agent Snapshot Key', action: 'Action: encrypt the new agent snapshot for owner restore' },
|
|
71
|
+
'update-snapshot': { title: 'Save Snapshot Encryption Key', action: 'Action: encrypt the updated agent snapshot' },
|
|
72
|
+
'update-ens-snapshot': { title: 'Update ENS in Agent Snapshot', action: 'Action: encrypt the snapshot with the new ENS name. No onchain ENS records change.' },
|
|
73
|
+
'clear-ens-snapshot': { title: 'Unlink ENS from Agent', action: 'Action: encrypt the snapshot with no ENS name. No onchain ENS records change.' },
|
|
74
|
+
'update-profile-snapshot': { title: 'Update Public Profile Snapshot Key', action: 'Action: encrypt the snapshot with the updated profile' },
|
|
75
|
+
'update-operators-snapshot': { title: 'Update Operator Wallets Snapshot Key', action: 'Action: encrypt the snapshot with the updated operator list' },
|
|
76
|
+
'refetch-snapshot': { title: 'Refetch Latest Snapshot', action: 'Action: decrypt the latest published snapshot' },
|
|
77
|
+
'operator-proof': { title: 'Authorize Operator Wallet Restore Access', action: 'Action: prove this operator wallet can decrypt future snapshots' },
|
|
78
|
+
'restore-owner': { title: 'Restore Agent with Owner Wallet', action: 'Action: decrypt the snapshot for the owner wallet' },
|
|
79
|
+
'restore-operator': { title: 'Restore Agent with Operator Wallet', action: 'Action: decrypt the snapshot for the authorized operator wallet' },
|
|
80
|
+
'transfer-prepare-sender': { title: 'Prepare Token Transfer (Sender)', action: 'Action: encrypt the transfer snapshot for the receiver' },
|
|
81
|
+
'transfer-prepare-receiver': { title: 'Receive Token Transfer (Receiver)', action: 'Action: prepare receiver decryption for the transfer snapshot' },
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function createWalletRestoreAccessChallenge(args: {
|
|
85
|
+
token: ContinuitySnapshotToken
|
|
86
|
+
ownerAddress: string
|
|
87
|
+
walletAddress: string
|
|
88
|
+
accessEpoch?: number
|
|
89
|
+
purpose?: WalletChallengePurpose
|
|
90
|
+
}): string {
|
|
91
|
+
const token = normalizeContinuitySnapshotToken(args.token)
|
|
92
|
+
const ownerAddress = toChecksumAddress(args.ownerAddress)
|
|
93
|
+
const walletAddress = toChecksumAddress(args.walletAddress)
|
|
94
|
+
if (args.purpose) {
|
|
95
|
+
const copy = WALLET_CHALLENGE_V2_COPY[args.purpose]
|
|
96
|
+
return [
|
|
97
|
+
copy.title,
|
|
98
|
+
`ERC-8004 Chain ID: ${token.chainId}`,
|
|
99
|
+
`ERC-8004 Registry: ${token.identityRegistryAddress}`,
|
|
100
|
+
`ERC-8004 Token ID: ${token.agentId}`,
|
|
101
|
+
`Owner: ${ownerAddress}`,
|
|
102
|
+
`Wallet: ${walletAddress}`,
|
|
103
|
+
`Access Epoch: ${args.accessEpoch ?? 1}`,
|
|
104
|
+
copy.action,
|
|
105
|
+
'Private: SOUL.md, MEMORY.md, skills',
|
|
106
|
+
'Safety: no transaction, spending, or approvals',
|
|
107
|
+
'Version: 3',
|
|
108
|
+
].join('\n')
|
|
109
|
+
}
|
|
110
|
+
return [
|
|
111
|
+
'Authorize Wallet Restore Access',
|
|
112
|
+
`ERC-8004 Chain ID: ${token.chainId}`,
|
|
113
|
+
`ERC-8004 Registry: ${token.identityRegistryAddress}`,
|
|
114
|
+
`ERC-8004 Token ID: ${token.agentId}`,
|
|
115
|
+
`Owner: ${ownerAddress}`,
|
|
116
|
+
`Wallet: ${walletAddress}`,
|
|
117
|
+
`Access Epoch: ${args.accessEpoch ?? 1}`,
|
|
118
|
+
'Action: create a restore key for encrypted identity snapshots',
|
|
119
|
+
'Private: SOUL.md, MEMORY.md, skills',
|
|
120
|
+
'Safety: no transaction, spending, or approvals',
|
|
121
|
+
'Version: 2',
|
|
122
|
+
].join('\n')
|
|
123
|
+
}
|