ethagent 3.0.1 → 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 +32 -117
- 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/cli/main.tsx +7 -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/publicSkills.ts +5 -5
- 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/registry/erc8004/metadata.ts +31 -23
- 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
- package/src/ui/terminalTitle.ts +30 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
const WALLET_PAGE_ENTRY_FILE = 'page.tsx'
|
|
6
|
+
const WALLET_PAGE_MODULE_FILES = [
|
|
7
|
+
join('page', 'types.ts'),
|
|
8
|
+
join('page', 'html.ts'),
|
|
9
|
+
join('page', 'constants.ts'),
|
|
10
|
+
join('page', 'styles', 'base.ts'),
|
|
11
|
+
join('page', 'styles', 'components.ts'),
|
|
12
|
+
join('page', 'styles', 'responsive.ts'),
|
|
13
|
+
join('page', 'styles', 'index.ts'),
|
|
14
|
+
join('page', 'markup.ts'),
|
|
15
|
+
join('page', 'grainient.ts'),
|
|
16
|
+
join('page', 'state.ts'),
|
|
17
|
+
join('page', 'copy.ts'),
|
|
18
|
+
join('page', 'errorView.ts'),
|
|
19
|
+
join('page', 'walletProvider.ts'),
|
|
20
|
+
join('page', 'view.ts'),
|
|
21
|
+
join('page', 'controller.ts'),
|
|
22
|
+
] as const
|
|
23
|
+
|
|
24
|
+
export function walletPageSourceFiles(fromUrl = import.meta.url): string[] {
|
|
25
|
+
const sourceRoot = locateWalletPageSourceRoot(fromUrl)
|
|
26
|
+
return [
|
|
27
|
+
...WALLET_PAGE_MODULE_FILES.map(file => join(sourceRoot, file)),
|
|
28
|
+
join(sourceRoot, WALLET_PAGE_ENTRY_FILE),
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function walletPageSourceFile(relativeFile: string, fromUrl = import.meta.url): string {
|
|
33
|
+
return join(locateWalletPageSourceRoot(fromUrl), relativeFile)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function loadWalletPageRawSource(fromUrl = import.meta.url): string {
|
|
37
|
+
return walletPageSourceFiles(fromUrl).map(file => readFileSync(file, 'utf8')).join('\n')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function loadWalletPageSource(fromUrl = import.meta.url): string {
|
|
41
|
+
return stripWalletModuleSyntax(loadWalletPageRawSource(fromUrl))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function stripWalletModuleSyntax(source: string): string {
|
|
45
|
+
const out: string[] = []
|
|
46
|
+
let skippingImport = false
|
|
47
|
+
for (const line of source.split(/\r?\n/)) {
|
|
48
|
+
const trimmed = line.trim()
|
|
49
|
+
if (skippingImport) {
|
|
50
|
+
if (/\bfrom\s+['"][^'"]+['"]/.test(trimmed) || trimmed.endsWith(';')) skippingImport = false
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
if (trimmed.startsWith('import ')) {
|
|
54
|
+
if (!/\bfrom\s+['"][^'"]+['"]/.test(trimmed) && !trimmed.endsWith(';')) skippingImport = true
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
out.push(line.replace(/^export\s+(?=(async\s+function|const|let|function|interface|type|class)\b)/, ''))
|
|
58
|
+
}
|
|
59
|
+
return out.join('\n')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function locateWalletPageSourceRoot(fromUrl: string): string {
|
|
63
|
+
for (const candidate of walletPageSourceRootCandidates(fromUrl)) {
|
|
64
|
+
if (hasWalletPageSourceFiles(candidate)) return candidate
|
|
65
|
+
}
|
|
66
|
+
throw new Error('could not locate browser wallet page source files')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function walletPageSourceRootCandidates(fromUrl: string): string[] {
|
|
70
|
+
const start = dirname(fileURLToPath(fromUrl))
|
|
71
|
+
const candidates = [join(start, '..')]
|
|
72
|
+
for (let dir = start; ; dir = dirname(dir)) {
|
|
73
|
+
candidates.push(join(dir, 'src', 'identity', 'wallet'))
|
|
74
|
+
const parent = dirname(dir)
|
|
75
|
+
if (parent === dir) break
|
|
76
|
+
}
|
|
77
|
+
return Array.from(new Set(candidates))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hasWalletPageSourceFiles(sourceRoot: string): boolean {
|
|
81
|
+
return [
|
|
82
|
+
...WALLET_PAGE_MODULE_FILES,
|
|
83
|
+
WALLET_PAGE_ENTRY_FILE,
|
|
84
|
+
].every(file => existsSync(join(sourceRoot, file)))
|
|
85
|
+
}
|
|
@@ -11,12 +11,12 @@ import {
|
|
|
11
11
|
errorSlot,
|
|
12
12
|
getLastWalletError,
|
|
13
13
|
initializeViewElements,
|
|
14
|
-
serializeWalletError,
|
|
15
14
|
setState,
|
|
16
15
|
showPreparedMessage,
|
|
17
16
|
statusHint,
|
|
18
17
|
statusText,
|
|
19
18
|
} from './view.js'
|
|
19
|
+
import { serializeWalletError } from './errorView.js'
|
|
20
20
|
import {
|
|
21
21
|
buildTxParams,
|
|
22
22
|
clearCurrentWalletMethod,
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { config } from './state.js'
|
|
2
|
+
import type { WalletErrorPayload } from './types.js'
|
|
3
|
+
import { escapeHtml } from './html.js'
|
|
4
|
+
import {
|
|
5
|
+
chainLabel,
|
|
6
|
+
PURPOSE_COPY,
|
|
7
|
+
type PurposeCopyEntry,
|
|
8
|
+
} from './copy.js'
|
|
9
|
+
|
|
10
|
+
export function serializeWalletError(err: unknown, method: string | null): WalletErrorPayload {
|
|
11
|
+
const e = err as any
|
|
12
|
+
const message = ((e && (e.message ?? String(err))) || 'Something went wrong.').trim()
|
|
13
|
+
const codeRaw = e && (e.code ?? e.errorCode)
|
|
14
|
+
const code = codeRaw === undefined || codeRaw === null ? undefined : String(codeRaw)
|
|
15
|
+
const data = safeStringify(e && e.data, 500)
|
|
16
|
+
const causes: string[] = []
|
|
17
|
+
let cur = e && e.cause
|
|
18
|
+
for (let i = 0; i < 5 && cur; i++) {
|
|
19
|
+
const cm = ((cur && (cur.message ?? String(cur))) || '').trim()
|
|
20
|
+
if (cm) causes.push(cm)
|
|
21
|
+
cur = cur && cur.cause
|
|
22
|
+
}
|
|
23
|
+
const out: WalletErrorPayload = { message }
|
|
24
|
+
if (code) out.code = code
|
|
25
|
+
if (data) out.data = data
|
|
26
|
+
if (causes.length) out.causes = causes
|
|
27
|
+
if (method) out.method = method
|
|
28
|
+
if (config.purpose) out.purpose = config.purpose
|
|
29
|
+
if (config.chainIdHex) out.chainIdHex = config.chainIdHex
|
|
30
|
+
return out
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function walletErrorHtml(payload: WalletErrorPayload): string {
|
|
34
|
+
const msg = payload.message || 'Something went wrong.'
|
|
35
|
+
const isNoWallet = /no wallet|window\.ethereum|metamask|rabby|brave|extension/i.test(msg)
|
|
36
|
+
const isUserReject = /user rejected|user denied|cancelled|canceled/i.test(msg)
|
|
37
|
+
const isWrongChain = /chain|network/i.test(msg) && !isNoWallet
|
|
38
|
+
const isExecutionRevert = /execution reverted|revert/i.test(msg)
|
|
39
|
+
const isOwnerWalletRequired = /owner wallet required/i.test(msg)
|
|
40
|
+
const isOperatorWalletRequired = /operator wallet required/i.test(msg)
|
|
41
|
+
let title = 'Wallet Error'
|
|
42
|
+
let body = msg
|
|
43
|
+
let hint = 'Press <code>enter</code> to retry or <code>esc</code> to abort.'
|
|
44
|
+
const codeClass = classifyByCode(payload.code)
|
|
45
|
+
if (codeClass) {
|
|
46
|
+
title = codeClass.title
|
|
47
|
+
hint = codeClass.hint
|
|
48
|
+
} else if (isOwnerWalletRequired) {
|
|
49
|
+
title = 'Owner Wallet Required'
|
|
50
|
+
body = msg.replace(/^owner wallet required:\s*/i, '')
|
|
51
|
+
body = body ? body.charAt(0).toUpperCase() + body.slice(1) : 'Switch to the owner wallet.'
|
|
52
|
+
hint = 'Switch to the owner wallet, then retry.'
|
|
53
|
+
} else if (isOperatorWalletRequired) {
|
|
54
|
+
title = 'Operator Wallet Required'
|
|
55
|
+
body = msg.replace(/^operator wallet required:\s*/i, '')
|
|
56
|
+
body = body ? body.charAt(0).toUpperCase() + body.slice(1) : 'Switch to the operator wallet.'
|
|
57
|
+
hint = 'Switch to the operator wallet, then retry.'
|
|
58
|
+
} else if (isNoWallet) {
|
|
59
|
+
title = 'No Wallet'
|
|
60
|
+
body = 'Install a wallet.'
|
|
61
|
+
hint = 'Install a wallet, then retry.'
|
|
62
|
+
} else if (isUserReject) {
|
|
63
|
+
title = 'Rejected'
|
|
64
|
+
body = 'Request declined in wallet.'
|
|
65
|
+
hint = 'Press <code>enter</code> to retry or <code>esc</code> to abort.'
|
|
66
|
+
} else if (isWrongChain) {
|
|
67
|
+
title = 'Wrong Network'
|
|
68
|
+
hint = `Switch to <code>${escapeHtml(chainLabel(config.chainIdHex))}</code>, then retry.`
|
|
69
|
+
} else if (isExecutionRevert) {
|
|
70
|
+
title = 'Transaction Reverted'
|
|
71
|
+
hint = 'Use the expected wallet and check ENS ownership, then retry.'
|
|
72
|
+
}
|
|
73
|
+
if (body) body = body.charAt(0).toUpperCase() + body.slice(1)
|
|
74
|
+
payload.title = title
|
|
75
|
+
const action = actionContextFor(payload)
|
|
76
|
+
let html = `<p class="error-title">${escapeHtml(title)}</p>`
|
|
77
|
+
+ `<p class="error-msg">${escapeHtml(body)}</p>`
|
|
78
|
+
if (action) html += `<p class="error-action">${escapeHtml(action)}</p>`
|
|
79
|
+
if (payload.causes && payload.causes.length) {
|
|
80
|
+
for (const cause of payload.causes) {
|
|
81
|
+
html += `<p class="error-cause">Caused by: ${escapeHtml(cause)}</p>`
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
html += `<p class="error-hint">${hint}</p>`
|
|
85
|
+
return html
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function safeStringify(v: unknown, max: number): string | undefined {
|
|
89
|
+
if (v === undefined || v === null) return undefined
|
|
90
|
+
let s: string
|
|
91
|
+
try { s = typeof v === 'string' ? v : JSON.stringify(v) } catch (_) { s = String(v) }
|
|
92
|
+
if (!s) return undefined
|
|
93
|
+
return s.length > max ? s.slice(0, max) + '...' : s
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function actionContextFor(payload: WalletErrorPayload): string {
|
|
97
|
+
const purpose = payload.purpose || config.purpose
|
|
98
|
+
if (purpose) {
|
|
99
|
+
const copy = (PURPOSE_COPY as any)[purpose] as PurposeCopyEntry | undefined
|
|
100
|
+
const ctx = copy && (copy as any).errorContext
|
|
101
|
+
if (typeof ctx === 'string' && ctx) return ctx
|
|
102
|
+
}
|
|
103
|
+
if (payload.method) return `during ${payload.method}`
|
|
104
|
+
return ''
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function classifyByCode(code: string | undefined): { title: string; hint: string } | null {
|
|
108
|
+
if (!code) return null
|
|
109
|
+
switch (code) {
|
|
110
|
+
case '4001': return { title: 'Rejected', hint: 'Press <code>enter</code> to retry or <code>esc</code> to abort.' }
|
|
111
|
+
case '4100': return { title: 'Wallet Not Authorized', hint: 'Connect this site in your wallet, then retry.' }
|
|
112
|
+
case '4200': return { title: 'Method Not Supported by Wallet', hint: 'Use a wallet that supports this transaction type, then retry.' }
|
|
113
|
+
case '4900': return { title: 'Wallet Disconnected', hint: 'Reconnect your wallet, then retry.' }
|
|
114
|
+
case '4901': return { title: 'Wrong Network', hint: `Switch to <code>${escapeHtml(chainLabel(config.chainIdHex))}</code>, then retry.` }
|
|
115
|
+
case '-32603': return { title: 'Internal Wallet RPC Error', hint: "The wallet's connected RPC failed. Try again, or switch RPC in your wallet settings." }
|
|
116
|
+
case '-32602': return { title: 'Invalid Request Parameters', hint: 'Press <code>enter</code> to retry or <code>esc</code> to abort.' }
|
|
117
|
+
case '-32000': case '-32001': case '-32002': case '-32003': case '-32004':
|
|
118
|
+
case '-32005': case '-32006': case '-32007': case '-32008': case '-32009':
|
|
119
|
+
return { title: 'Wallet RPC Error', hint: 'The wallet RPC reported a server error. Try again or switch RPC in your wallet.' }
|
|
120
|
+
default: return null
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { config } from './state.js'
|
|
2
2
|
import type { WalletErrorPayload } from './types.js'
|
|
3
|
-
import {
|
|
3
|
+
import { glyphs } from './html.js'
|
|
4
4
|
import {
|
|
5
5
|
accountCopy,
|
|
6
6
|
chainLabel,
|
|
7
7
|
FLOW_COPY,
|
|
8
8
|
isTransactionFlow,
|
|
9
|
-
PURPOSE_COPY,
|
|
10
9
|
purposeCopy,
|
|
11
10
|
signCopy,
|
|
12
11
|
STATE_TITLES,
|
|
@@ -14,8 +13,8 @@ import {
|
|
|
14
13
|
transactionCopy,
|
|
15
14
|
transactionPurposeTitle,
|
|
16
15
|
type FlowCopy,
|
|
17
|
-
type PurposeCopyEntry,
|
|
18
16
|
} from './copy.js'
|
|
17
|
+
import { walletErrorHtml } from './errorView.js'
|
|
19
18
|
|
|
20
19
|
let card: HTMLElement;
|
|
21
20
|
let promptText: HTMLElement;
|
|
@@ -416,118 +415,8 @@ export function setState(state: string, payload?: any): void {
|
|
|
416
415
|
|
|
417
416
|
let lastWalletError: WalletErrorPayload | null = null;
|
|
418
417
|
|
|
419
|
-
function safeStringify(v: unknown, max: number): string | undefined {
|
|
420
|
-
if (v === undefined || v === null) return undefined;
|
|
421
|
-
let s: string;
|
|
422
|
-
try { s = typeof v === "string" ? v : JSON.stringify(v); } catch (_) { s = String(v); }
|
|
423
|
-
if (!s) return undefined;
|
|
424
|
-
return s.length > max ? s.slice(0, max) + "..." : s;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
export function serializeWalletError(err: unknown, method: string | null): WalletErrorPayload {
|
|
428
|
-
const e = err as any;
|
|
429
|
-
const message = ((e && (e.message ?? String(err))) || "Something went wrong.").trim();
|
|
430
|
-
const codeRaw = e && (e.code ?? e.errorCode);
|
|
431
|
-
const code = codeRaw === undefined || codeRaw === null ? undefined : String(codeRaw);
|
|
432
|
-
const data = safeStringify(e && e.data, 500);
|
|
433
|
-
const causes: string[] = [];
|
|
434
|
-
let cur = e && e.cause;
|
|
435
|
-
for (let i = 0; i < 5 && cur; i++) {
|
|
436
|
-
const cm = ((cur && (cur.message ?? String(cur))) || "").trim();
|
|
437
|
-
if (cm) causes.push(cm);
|
|
438
|
-
cur = cur && cur.cause;
|
|
439
|
-
}
|
|
440
|
-
const out: WalletErrorPayload = { message };
|
|
441
|
-
if (code) out.code = code;
|
|
442
|
-
if (data) out.data = data;
|
|
443
|
-
if (causes.length) out.causes = causes;
|
|
444
|
-
if (method) out.method = method;
|
|
445
|
-
if (config.purpose) out.purpose = config.purpose;
|
|
446
|
-
if (config.chainIdHex) out.chainIdHex = config.chainIdHex;
|
|
447
|
-
return out;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function actionContextFor(payload: WalletErrorPayload): string {
|
|
451
|
-
const purpose = payload.purpose || config.purpose;
|
|
452
|
-
if (purpose) {
|
|
453
|
-
const copy = (PURPOSE_COPY as any)[purpose] as PurposeCopyEntry | undefined;
|
|
454
|
-
const ctx = copy && (copy as any).errorContext;
|
|
455
|
-
if (typeof ctx === "string" && ctx) return ctx;
|
|
456
|
-
}
|
|
457
|
-
if (payload.method) return "during " + payload.method;
|
|
458
|
-
return "";
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
function classifyByCode(code: string | undefined): { title: string; hint: string } | null {
|
|
462
|
-
if (!code) return null;
|
|
463
|
-
switch (code) {
|
|
464
|
-
case "4001": return { title: "Rejected", hint: "Press <code>enter</code> to retry or <code>esc</code> to abort." };
|
|
465
|
-
case "4100": return { title: "Wallet Not Authorized", hint: "Connect this site in your wallet, then retry." };
|
|
466
|
-
case "4200": return { title: "Method Not Supported by Wallet", hint: "Use a wallet that supports this transaction type, then retry." };
|
|
467
|
-
case "4900": return { title: "Wallet Disconnected", hint: "Reconnect your wallet, then retry." };
|
|
468
|
-
case "4901": return { title: "Wrong Network", hint: "Switch to <code>" + escapeHtml(chainLabel(config.chainIdHex)) + "</code>, then retry." };
|
|
469
|
-
case "-32603": return { title: "Internal Wallet RPC Error", hint: "The wallet's connected RPC failed. Try again, or switch RPC in your wallet settings." };
|
|
470
|
-
case "-32602": return { title: "Invalid Request Parameters", hint: "Press <code>enter</code> to retry or <code>esc</code> to abort." };
|
|
471
|
-
case "-32000": case "-32001": case "-32002": case "-32003": case "-32004":
|
|
472
|
-
case "-32005": case "-32006": case "-32007": case "-32008": case "-32009":
|
|
473
|
-
return { title: "Wallet RPC Error", hint: "The wallet RPC reported a server error. Try again or switch RPC in your wallet." };
|
|
474
|
-
default: return null;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
418
|
function renderError(payload: WalletErrorPayload): void {
|
|
479
|
-
|
|
480
|
-
const isNoWallet = /no wallet|window\.ethereum|metamask|rabby|brave|extension/i.test(msg);
|
|
481
|
-
const isUserReject = /user rejected|user denied|cancelled|canceled/i.test(msg);
|
|
482
|
-
const isWrongChain = /chain|network/i.test(msg) && !isNoWallet;
|
|
483
|
-
const isExecutionRevert = /execution reverted|revert/i.test(msg);
|
|
484
|
-
const isOwnerWalletRequired = /owner wallet required/i.test(msg);
|
|
485
|
-
const isOperatorWalletRequired = /operator wallet required/i.test(msg);
|
|
486
|
-
let title = "Wallet Error";
|
|
487
|
-
let body = msg;
|
|
488
|
-
let hint = "Press <code>enter</code> to retry or <code>esc</code> to abort.";
|
|
489
|
-
const codeClass = classifyByCode(payload.code);
|
|
490
|
-
if (codeClass) {
|
|
491
|
-
title = codeClass.title;
|
|
492
|
-
hint = codeClass.hint;
|
|
493
|
-
} else if (isOwnerWalletRequired) {
|
|
494
|
-
title = "Owner Wallet Required";
|
|
495
|
-
body = msg.replace(/^owner wallet required:\s*/i, "");
|
|
496
|
-
body = body ? body.charAt(0).toUpperCase() + body.slice(1) : "Switch to the owner wallet.";
|
|
497
|
-
hint = "Switch to the owner wallet, then retry.";
|
|
498
|
-
} else if (isOperatorWalletRequired) {
|
|
499
|
-
title = "Operator Wallet Required";
|
|
500
|
-
body = msg.replace(/^operator wallet required:\s*/i, "");
|
|
501
|
-
body = body ? body.charAt(0).toUpperCase() + body.slice(1) : "Switch to the operator wallet.";
|
|
502
|
-
hint = "Switch to the operator wallet, then retry.";
|
|
503
|
-
} else if (isNoWallet) {
|
|
504
|
-
title = "No Wallet";
|
|
505
|
-
body = "Install a wallet.";
|
|
506
|
-
hint = "Install a wallet, then retry.";
|
|
507
|
-
} else if (isUserReject) {
|
|
508
|
-
title = "Rejected";
|
|
509
|
-
body = "Request declined in wallet.";
|
|
510
|
-
hint = "Press <code>enter</code> to retry or <code>esc</code> to abort.";
|
|
511
|
-
} else if (isWrongChain) {
|
|
512
|
-
title = "Wrong Network";
|
|
513
|
-
hint = "Switch to <code>" + escapeHtml(chainLabel(config.chainIdHex)) + "</code>, then retry.";
|
|
514
|
-
} else if (isExecutionRevert) {
|
|
515
|
-
title = "Transaction Reverted";
|
|
516
|
-
hint = "Use the expected wallet and check ENS ownership, then retry.";
|
|
517
|
-
}
|
|
518
|
-
if (body) body = body.charAt(0).toUpperCase() + body.slice(1);
|
|
519
|
-
payload.title = title;
|
|
520
|
-
const action = actionContextFor(payload);
|
|
521
|
-
let html = '<p class="error-title">' + escapeHtml(title) + "</p>"
|
|
522
|
-
+ '<p class="error-msg">' + escapeHtml(body) + "</p>";
|
|
523
|
-
if (action) html += '<p class="error-action">' + escapeHtml(action) + "</p>";
|
|
524
|
-
if (payload.causes && payload.causes.length) {
|
|
525
|
-
for (const cause of payload.causes) {
|
|
526
|
-
html += '<p class="error-cause">Caused by: ' + escapeHtml(cause) + "</p>";
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
html += '<p class="error-hint">' + hint + "</p>";
|
|
530
|
-
errorSlot.innerHTML = html;
|
|
419
|
+
errorSlot.innerHTML = walletErrorHtml(payload);
|
|
531
420
|
lastWalletError = payload;
|
|
532
421
|
}
|
|
533
422
|
|
package/src/mcp/manager.ts
CHANGED
|
@@ -2,9 +2,6 @@ import { Ajv } from 'ajv'
|
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
|
|
4
4
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
5
|
-
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
6
|
-
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
|
|
7
|
-
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
|
8
5
|
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'
|
|
9
6
|
import type { Tool, ToolResult } from '../tools/contracts.js'
|
|
10
7
|
import {
|
|
@@ -28,12 +25,19 @@ import {
|
|
|
28
25
|
formatMcpResourceResult,
|
|
29
26
|
promptMessagesToText,
|
|
30
27
|
} from './output.js'
|
|
28
|
+
import {
|
|
29
|
+
createTransport,
|
|
30
|
+
findScopedServer,
|
|
31
|
+
findServerSnapshot,
|
|
32
|
+
normalizeInputSchemaJson,
|
|
33
|
+
parsePromptArgs,
|
|
34
|
+
} from './managerHelpers.js'
|
|
31
35
|
|
|
32
36
|
const MCP_CONNECT_TIMEOUT_MS = 10_000
|
|
33
37
|
const MCP_LIST_TIMEOUT_MS = 10_000
|
|
34
38
|
const MCP_TOOL_TIMEOUT_MS = 120_000
|
|
35
39
|
|
|
36
|
-
type ListedMcpTool = {
|
|
40
|
+
export type ListedMcpTool = {
|
|
37
41
|
name: string
|
|
38
42
|
description?: string
|
|
39
43
|
inputSchema: {
|
|
@@ -476,65 +480,3 @@ export class McpManager implements McpRuntime {
|
|
|
476
480
|
this.tools = []
|
|
477
481
|
}
|
|
478
482
|
}
|
|
479
|
-
|
|
480
|
-
function createTransport(config: McpServerConfig, cwd: string): Transport {
|
|
481
|
-
if (config.type === 'http') {
|
|
482
|
-
return new StreamableHTTPClientTransport(new URL(config.url), {
|
|
483
|
-
requestInit: config.headers ? { headers: config.headers } : undefined,
|
|
484
|
-
})
|
|
485
|
-
}
|
|
486
|
-
if (config.type === 'sse') {
|
|
487
|
-
return new SSEClientTransport(new URL(config.url), {
|
|
488
|
-
requestInit: config.headers ? { headers: config.headers } : undefined,
|
|
489
|
-
eventSourceInit: config.headers ? { fetch: (url, init) => fetch(url, { ...init, headers: config.headers }) } : undefined,
|
|
490
|
-
})
|
|
491
|
-
}
|
|
492
|
-
return new StdioClientTransport({
|
|
493
|
-
command: config.command,
|
|
494
|
-
args: config.args ?? [],
|
|
495
|
-
env: config.env ? mergeProcessEnv(config.env) : undefined,
|
|
496
|
-
cwd: config.cwd ?? cwd,
|
|
497
|
-
stderr: 'pipe',
|
|
498
|
-
})
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
function mergeProcessEnv(extra: Record<string, string>): Record<string, string> {
|
|
502
|
-
const env: Record<string, string> = {}
|
|
503
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
504
|
-
if (value !== undefined) env[key] = value
|
|
505
|
-
}
|
|
506
|
-
return { ...env, ...extra }
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function normalizeInputSchemaJson(schema: ListedMcpTool['inputSchema']): Tool['inputSchemaJson'] {
|
|
510
|
-
return {
|
|
511
|
-
type: 'object',
|
|
512
|
-
properties: schema.properties,
|
|
513
|
-
required: schema.required,
|
|
514
|
-
oneOf: Array.isArray(schema.oneOf) ? schema.oneOf as Array<Record<string, unknown>> : undefined,
|
|
515
|
-
anyOf: Array.isArray(schema.anyOf) ? schema.anyOf as Array<Record<string, unknown>> : undefined,
|
|
516
|
-
additionalProperties: schema.additionalProperties as boolean | undefined,
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
function findScopedServer(servers: ScopedMcpServerConfig[], name: string): ScopedMcpServerConfig | undefined {
|
|
521
|
-
const normalized = normalizeNameForMcp(name)
|
|
522
|
-
return servers.find(server => server.name === name || normalizeNameForMcp(server.name) === normalized)
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function findServerSnapshot(servers: McpServerSnapshot[], name: string): McpServerSnapshot | undefined {
|
|
526
|
-
const normalized = normalizeNameForMcp(name)
|
|
527
|
-
return servers.find(server => server.name === name || server.normalizedName === normalized)
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
function parsePromptArgs(value: string): Record<string, string> {
|
|
531
|
-
const args: Record<string, string> = {}
|
|
532
|
-
for (const token of value.trim().split(/\s+/).filter(Boolean)) {
|
|
533
|
-
const idx = token.indexOf('=')
|
|
534
|
-
if (idx === -1) continue
|
|
535
|
-
const key = token.slice(0, idx)
|
|
536
|
-
if (!key) continue
|
|
537
|
-
args[key] = token.slice(idx + 1)
|
|
538
|
-
}
|
|
539
|
-
return args
|
|
540
|
-
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
|
|
2
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
3
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
|
|
4
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
|
5
|
+
import type { Tool } from '../tools/contracts.js'
|
|
6
|
+
import type { McpServerConfig, ScopedMcpServerConfig } from './config.js'
|
|
7
|
+
import { normalizeNameForMcp } from './names.js'
|
|
8
|
+
import type { ListedMcpTool, McpServerSnapshot } from './manager.js'
|
|
9
|
+
|
|
10
|
+
export function createTransport(config: McpServerConfig, cwd: string): Transport {
|
|
11
|
+
if (config.type === 'http') {
|
|
12
|
+
return new StreamableHTTPClientTransport(new URL(config.url), {
|
|
13
|
+
requestInit: config.headers ? { headers: config.headers } : undefined,
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
if (config.type === 'sse') {
|
|
17
|
+
return new SSEClientTransport(new URL(config.url), {
|
|
18
|
+
requestInit: config.headers ? { headers: config.headers } : undefined,
|
|
19
|
+
eventSourceInit: config.headers ? { fetch: (url, init) => fetch(url, { ...init, headers: config.headers }) } : undefined,
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
return new StdioClientTransport({
|
|
23
|
+
command: config.command,
|
|
24
|
+
args: config.args ?? [],
|
|
25
|
+
env: config.env ? mergeProcessEnv(config.env) : undefined,
|
|
26
|
+
cwd: config.cwd ?? cwd,
|
|
27
|
+
stderr: 'pipe',
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function normalizeInputSchemaJson(schema: ListedMcpTool['inputSchema']): Tool['inputSchemaJson'] {
|
|
32
|
+
return {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: schema.properties,
|
|
35
|
+
required: schema.required,
|
|
36
|
+
oneOf: Array.isArray(schema.oneOf) ? schema.oneOf as Array<Record<string, unknown>> : undefined,
|
|
37
|
+
anyOf: Array.isArray(schema.anyOf) ? schema.anyOf as Array<Record<string, unknown>> : undefined,
|
|
38
|
+
additionalProperties: schema.additionalProperties as boolean | undefined,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function findScopedServer(servers: ScopedMcpServerConfig[], name: string): ScopedMcpServerConfig | undefined {
|
|
43
|
+
const normalized = normalizeNameForMcp(name)
|
|
44
|
+
return servers.find(server => server.name === name || normalizeNameForMcp(server.name) === normalized)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function findServerSnapshot(servers: McpServerSnapshot[], name: string): McpServerSnapshot | undefined {
|
|
48
|
+
const normalized = normalizeNameForMcp(name)
|
|
49
|
+
return servers.find(server => server.name === name || server.normalizedName === normalized)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function parsePromptArgs(value: string): Record<string, string> {
|
|
53
|
+
const args: Record<string, string> = {}
|
|
54
|
+
for (const token of value.trim().split(/\s+/).filter(Boolean)) {
|
|
55
|
+
const idx = token.indexOf('=')
|
|
56
|
+
if (idx === -1) continue
|
|
57
|
+
const key = token.slice(0, idx)
|
|
58
|
+
if (!key) continue
|
|
59
|
+
args[key] = token.slice(idx + 1)
|
|
60
|
+
}
|
|
61
|
+
return args
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function mergeProcessEnv(extra: Record<string, string>): Record<string, string> {
|
|
65
|
+
const env: Record<string, string> = {}
|
|
66
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
67
|
+
if (value !== undefined) env[key] = value
|
|
68
|
+
}
|
|
69
|
+
return { ...env, ...extra }
|
|
70
|
+
}
|