lyra-core 0.1.2 → 0.1.10
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 +3 -15
- package/src/brain/compaction.test.ts +0 -156
- package/src/brain/frozen-prefix.test.ts +0 -235
- package/src/brain/history-persist.test.ts +0 -129
- package/src/brain/openai-brain.test.ts +0 -61
- package/src/brain/sanitize.test.ts +0 -27
- package/src/claude-plugins/discovery.test.ts +0 -71
- package/src/commands/registry.test.ts +0 -186
- package/src/events/queue.test.ts +0 -43
- package/src/index.test.ts +0 -6
- package/src/locks.test.ts +0 -259
- package/src/mcp/discovery.test.ts +0 -97
- package/src/mcp/manager.test.ts +0 -97
- package/src/memory/edit.test.ts +0 -41
- package/src/memory/encryption.test.ts +0 -68
- package/src/memory/index-sync.test.ts +0 -81
- package/src/memory/pack-blob.test.ts +0 -90
- package/src/memory/pack-gather.test.ts +0 -110
- package/src/memory/parser.test.ts +0 -29
- package/src/memory/path-drift.test.ts +0 -71
- package/src/memory/read-tool-fallback.test.ts +0 -54
- package/src/memory/save-tool.test.ts +0 -193
- package/src/memory/scan.test.ts +0 -24
- package/src/migration/option3-crypto.test.ts +0 -106
- package/src/pairing.test.ts +0 -212
- package/src/permission/permission.test.ts +0 -299
- package/src/plugins/plugins.test.ts +0 -196
- package/src/public/card.test.ts +0 -70
- package/src/runtime/runtime.test.ts +0 -55
- package/src/sandbox/sandbox.test.ts +0 -386
- package/src/skills/scanner.test.ts +0 -137
- package/src/skills/triggers.test.ts +0 -77
- package/src/storage/encryption.test.ts +0 -30
- package/src/storage/sqlite.test.ts +0 -29
- package/src/tools/escalation.test.ts +0 -348
- package/src/tools/registry.test.ts +0 -70
- package/src/tools/zod-helpers.test.ts +0 -63
- package/src/wallet/drain.test.ts +0 -41
- package/src/wallet/keystore.test.ts +0 -17
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
import { expect, test } from 'bun:test'
|
|
2
|
-
import {
|
|
3
|
-
type EscalationDeps,
|
|
4
|
-
type FetchEscalation,
|
|
5
|
-
detectFetchEscalation,
|
|
6
|
-
mergeEscalationResult,
|
|
7
|
-
runEscalation,
|
|
8
|
-
} from './escalation'
|
|
9
|
-
import type { ToolCall, ToolResult } from './types'
|
|
10
|
-
|
|
11
|
-
const fetchCall = (id: string, url: string): ToolCall => ({
|
|
12
|
-
id,
|
|
13
|
-
name: 'web.fetch',
|
|
14
|
-
args: { url },
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
const blockedResult = (final_url: string, reason: string): ToolResult => ({
|
|
18
|
-
ok: true,
|
|
19
|
-
data: {
|
|
20
|
-
status: 200,
|
|
21
|
-
content_type: 'text/html',
|
|
22
|
-
body: '# unusual traffic from your computer network\n\nplease show you are not a robot',
|
|
23
|
-
truncated: false,
|
|
24
|
-
final_url,
|
|
25
|
-
blocked: true,
|
|
26
|
-
block_reason: reason,
|
|
27
|
-
},
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
const escalationFor = (call: ToolCall, reason: string, url: string): FetchEscalation => ({
|
|
31
|
-
needed: true,
|
|
32
|
-
escalatedCall: { id: `auto-escalate-${call.id}`, name: 'browser.navigate', args: { url } },
|
|
33
|
-
reason,
|
|
34
|
-
url,
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test('detect: web.fetch + blocked + final_url returns needed with escalated call', () => {
|
|
38
|
-
const call = fetchCall('c1', 'https://www.google.com/search?q=test')
|
|
39
|
-
const result = blockedResult('https://www.google.com/search?q=test', 'google-bot-block')
|
|
40
|
-
const out = detectFetchEscalation(call, result)
|
|
41
|
-
expect(out.needed).toBe(true)
|
|
42
|
-
expect(out.escalatedCall?.id).toBe('auto-escalate-c1')
|
|
43
|
-
expect(out.escalatedCall?.name).toBe('browser.navigate')
|
|
44
|
-
expect(out.escalatedCall?.args).toEqual({ url: 'https://www.google.com/search?q=test' })
|
|
45
|
-
expect(out.reason).toBe('google-bot-block')
|
|
46
|
-
expect(out.url).toBe('https://www.google.com/search?q=test')
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
test('detect: blocked + no final_url falls back to call.args.url', () => {
|
|
50
|
-
const call = fetchCall('c2', 'https://example.com/blocked')
|
|
51
|
-
const result: ToolResult = {
|
|
52
|
-
ok: true,
|
|
53
|
-
data: { blocked: true, block_reason: 'cloudflare', body: 'Just a moment...' },
|
|
54
|
-
}
|
|
55
|
-
const out = detectFetchEscalation(call, result)
|
|
56
|
-
expect(out.needed).toBe(true)
|
|
57
|
-
expect(out.escalatedCall?.args).toEqual({ url: 'https://example.com/blocked' })
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
test('detect: blocked + neither final_url nor args.url returns not needed', () => {
|
|
61
|
-
const call: ToolCall = { id: 'c3', name: 'web.fetch', args: {} }
|
|
62
|
-
const result: ToolResult = { ok: true, data: { blocked: true, block_reason: 'captcha' } }
|
|
63
|
-
expect(detectFetchEscalation(call, result).needed).toBe(false)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
test('detect: v0.21.3 — timeout failure DOES escalate (transient)', () => {
|
|
67
|
-
const call = fetchCall('c4', 'https://arxiv.org/search/?query=test')
|
|
68
|
-
const result: ToolResult = { ok: false, error: 'timeout after 15000ms' }
|
|
69
|
-
const out = detectFetchEscalation(call, result)
|
|
70
|
-
expect(out.needed).toBe(true)
|
|
71
|
-
expect(out.reason).toBe('timeout')
|
|
72
|
-
expect(out.escalatedCall?.args).toEqual({ url: 'https://arxiv.org/search/?query=test' })
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
test('detect: v0.21.3 — http 503 failure DOES escalate', () => {
|
|
76
|
-
const call = fetchCall('c4b', 'https://flaky.example.com')
|
|
77
|
-
const result: ToolResult = { ok: false, error: 'http 503' }
|
|
78
|
-
const out = detectFetchEscalation(call, result)
|
|
79
|
-
expect(out.needed).toBe(true)
|
|
80
|
-
expect(out.reason).toBe('http-503')
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
test('detect: v0.21.3 — http 429 failure DOES escalate', () => {
|
|
84
|
-
const call = fetchCall('c4c', 'https://api.example.com')
|
|
85
|
-
const result: ToolResult = { ok: false, error: 'http 429' }
|
|
86
|
-
const out = detectFetchEscalation(call, result)
|
|
87
|
-
expect(out.needed).toBe(true)
|
|
88
|
-
expect(out.reason).toBe('http-429')
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
test('detect: v0.21.3 — DNS failure DOES escalate', () => {
|
|
92
|
-
const call = fetchCall('c4d', 'https://dns-broken.example')
|
|
93
|
-
const result: ToolResult = {
|
|
94
|
-
ok: false,
|
|
95
|
-
error: 'getaddrinfo ENOTFOUND dns-broken.example',
|
|
96
|
-
}
|
|
97
|
-
const out = detectFetchEscalation(call, result)
|
|
98
|
-
expect(out.needed).toBe(true)
|
|
99
|
-
expect(out.reason).toBe('dns')
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
test('detect: v0.21.3 — connection refused DOES escalate', () => {
|
|
103
|
-
const call = fetchCall('c4e', 'https://refused.example.com')
|
|
104
|
-
const result: ToolResult = { ok: false, error: 'connect ECONNREFUSED' }
|
|
105
|
-
const out = detectFetchEscalation(call, result)
|
|
106
|
-
expect(out.needed).toBe(true)
|
|
107
|
-
expect(out.reason).toBe('connection')
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
test('detect: v0.21.3 — generic fetch failure DOES escalate', () => {
|
|
111
|
-
const call = fetchCall('c4f', 'https://example.com')
|
|
112
|
-
const result: ToolResult = { ok: false, error: 'fetch failed' }
|
|
113
|
-
const out = detectFetchEscalation(call, result)
|
|
114
|
-
expect(out.needed).toBe(true)
|
|
115
|
-
expect(out.reason).toBe('fetch-error')
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
test('detect: v0.21.3 — invalid URL is permanent, does NOT escalate', () => {
|
|
119
|
-
const call: ToolCall = { id: 'c4g', name: 'web.fetch', args: { url: 'not a url' } }
|
|
120
|
-
const result: ToolResult = { ok: false, error: 'invalid URL' }
|
|
121
|
-
expect(detectFetchEscalation(call, result).needed).toBe(false)
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
test('detect: v0.21.3 — unsupported protocol is permanent, does NOT escalate', () => {
|
|
125
|
-
const call = fetchCall('c4h', 'file:///etc/passwd')
|
|
126
|
-
const result: ToolResult = { ok: false, error: 'unsupported protocol: file:' }
|
|
127
|
-
expect(detectFetchEscalation(call, result).needed).toBe(false)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
test('detect: v0.21.3 — host blocked (private IP) is permanent, does NOT escalate', () => {
|
|
131
|
-
const call = fetchCall('c4i', 'http://169.254.169.254/latest/meta-data')
|
|
132
|
-
const result: ToolResult = {
|
|
133
|
-
ok: false,
|
|
134
|
-
error: 'host blocked (private/loopback/metadata): 169.254.169.254',
|
|
135
|
-
}
|
|
136
|
-
expect(detectFetchEscalation(call, result).needed).toBe(false)
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
test('detect: web.fetch ok with no blocked field returns not needed', () => {
|
|
140
|
-
const call = fetchCall('c5', 'https://news.ycombinator.com')
|
|
141
|
-
const result: ToolResult = {
|
|
142
|
-
ok: true,
|
|
143
|
-
data: { status: 200, body: '# Hacker News', final_url: 'https://news.ycombinator.com' },
|
|
144
|
-
}
|
|
145
|
-
expect(detectFetchEscalation(call, result).needed).toBe(false)
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
test('detect: non-web.fetch tool with blocked:true returns not needed', () => {
|
|
149
|
-
const call: ToolCall = { id: 'c6', name: 'shell.run', args: { command: 'ls' } }
|
|
150
|
-
const result: ToolResult = { ok: true, data: { blocked: true, block_reason: 'spurious' } }
|
|
151
|
-
expect(detectFetchEscalation(call, result).needed).toBe(false)
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
test('detect: web.fetch with missing data field + ok=true returns not needed', () => {
|
|
155
|
-
const call = fetchCall('c7', 'https://x.com')
|
|
156
|
-
const result: ToolResult = { ok: true }
|
|
157
|
-
expect(detectFetchEscalation(call, result).needed).toBe(false)
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
test('detect: web.fetch missing block_reason still escalates with reason "unknown"', () => {
|
|
161
|
-
const call = fetchCall('c8', 'https://x.com')
|
|
162
|
-
const result: ToolResult = {
|
|
163
|
-
ok: true,
|
|
164
|
-
data: { blocked: true, final_url: 'https://x.com' },
|
|
165
|
-
}
|
|
166
|
-
const out = detectFetchEscalation(call, result)
|
|
167
|
-
expect(out.needed).toBe(true)
|
|
168
|
-
expect(out.reason).toBe('unknown')
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
test('detect: tail-recursion guard refuses to escalate already-escalated calls', () => {
|
|
172
|
-
const call: ToolCall = {
|
|
173
|
-
id: 'auto-escalate-original',
|
|
174
|
-
name: 'web.fetch',
|
|
175
|
-
args: { url: 'https://x.com' },
|
|
176
|
-
}
|
|
177
|
-
const result = blockedResult('https://x.com', 'cloudflare')
|
|
178
|
-
expect(detectFetchEscalation(call, result).needed).toBe(false)
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
test('detect: v0.21.3 — failure with no URL anywhere does NOT escalate', () => {
|
|
182
|
-
const call: ToolCall = { id: 'c9', name: 'web.fetch', args: {} }
|
|
183
|
-
const result: ToolResult = { ok: false, error: 'timeout after 15000ms' }
|
|
184
|
-
expect(detectFetchEscalation(call, result).needed).toBe(false)
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
test('merge: ok escalation produces ok=true result with both blobs', () => {
|
|
188
|
-
const original = blockedResult('https://www.google.com', 'google-bot-block')
|
|
189
|
-
const escalated: ToolResult = {
|
|
190
|
-
ok: true,
|
|
191
|
-
data: { currentUrl: 'https://www.google.com', title: 'Google' },
|
|
192
|
-
}
|
|
193
|
-
const escalation = escalationFor(
|
|
194
|
-
fetchCall('c9', 'https://www.google.com'),
|
|
195
|
-
'google-bot-block',
|
|
196
|
-
'https://www.google.com',
|
|
197
|
-
)
|
|
198
|
-
const merged = mergeEscalationResult(original, escalated, escalation)
|
|
199
|
-
expect(merged.ok).toBe(true)
|
|
200
|
-
const data = merged.data as Record<string, unknown>
|
|
201
|
-
expect(data.blocked).toBe(true)
|
|
202
|
-
expect(data.body).toContain('unusual traffic')
|
|
203
|
-
const ae = data.auto_escalation as Record<string, unknown>
|
|
204
|
-
expect(ae.triggered).toBe(true)
|
|
205
|
-
expect(ae.from).toBe('web.fetch')
|
|
206
|
-
expect(ae.to).toBe('browser.navigate')
|
|
207
|
-
expect(ae.reason).toBe('google-bot-block')
|
|
208
|
-
expect(ae.url).toBe('https://www.google.com')
|
|
209
|
-
const aeResult = ae.result as ToolResult
|
|
210
|
-
expect(aeResult.ok).toBe(true)
|
|
211
|
-
expect((aeResult.data as Record<string, unknown>).title).toBe('Google')
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
test('merge: v0.21.3 — timeout escalation preserves original error', () => {
|
|
215
|
-
const original: ToolResult = { ok: false, error: 'timeout after 15000ms' }
|
|
216
|
-
const escalated: ToolResult = { ok: true, data: { currentUrl: 'https://arxiv.org/search' } }
|
|
217
|
-
const escalation = escalationFor(
|
|
218
|
-
fetchCall('cT', 'https://arxiv.org/search'),
|
|
219
|
-
'timeout',
|
|
220
|
-
'https://arxiv.org/search',
|
|
221
|
-
)
|
|
222
|
-
const merged = mergeEscalationResult(original, escalated, escalation)
|
|
223
|
-
expect(merged.ok).toBe(true)
|
|
224
|
-
const data = merged.data as Record<string, unknown>
|
|
225
|
-
const ae = data.auto_escalation as Record<string, unknown>
|
|
226
|
-
expect(ae.reason).toBe('timeout')
|
|
227
|
-
expect(ae.original_error).toBe('timeout after 15000ms')
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
test('merge: failed escalation sets ok=false with descriptive error, preserves original data', () => {
|
|
231
|
-
const original = blockedResult('https://www.google.com', 'google-bot-block')
|
|
232
|
-
const escalated: ToolResult = {
|
|
233
|
-
ok: false,
|
|
234
|
-
error: 'Unknown tool: browser.navigate',
|
|
235
|
-
}
|
|
236
|
-
const escalation = escalationFor(
|
|
237
|
-
fetchCall('c10', 'https://www.google.com'),
|
|
238
|
-
'google-bot-block',
|
|
239
|
-
'https://www.google.com',
|
|
240
|
-
)
|
|
241
|
-
const merged = mergeEscalationResult(original, escalated, escalation)
|
|
242
|
-
expect(merged.ok).toBe(false)
|
|
243
|
-
expect(merged.error).toContain('auto-escalation failed')
|
|
244
|
-
expect(merged.error).toContain('Unknown tool: browser.navigate')
|
|
245
|
-
const data = merged.data as Record<string, unknown>
|
|
246
|
-
expect(data.blocked).toBe(true)
|
|
247
|
-
expect(data.body).toContain('unusual traffic')
|
|
248
|
-
const ae = data.auto_escalation as Record<string, unknown>
|
|
249
|
-
expect((ae.result as ToolResult).ok).toBe(false)
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
test('merge: failed escalation with no error field still produces fallback message', () => {
|
|
253
|
-
const original = blockedResult('https://x.com', 'cloudflare')
|
|
254
|
-
const escalated: ToolResult = { ok: false }
|
|
255
|
-
const escalation = escalationFor(fetchCall('c11', 'https://x.com'), 'cloudflare', 'https://x.com')
|
|
256
|
-
const merged = mergeEscalationResult(original, escalated, escalation)
|
|
257
|
-
expect(merged.error).toBe('auto-escalation failed: unknown')
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
test('merge: missing original.data handled defensively (no throw, escalation block still set)', () => {
|
|
261
|
-
const original: ToolResult = { ok: true }
|
|
262
|
-
const escalated: ToolResult = { ok: true, data: { currentUrl: 'https://x.com' } }
|
|
263
|
-
const escalation = escalationFor(fetchCall('c12', 'https://x.com'), 'rate-limit', 'https://x.com')
|
|
264
|
-
const merged = mergeEscalationResult(original, escalated, escalation)
|
|
265
|
-
expect(merged.ok).toBe(true)
|
|
266
|
-
const data = merged.data as Record<string, unknown>
|
|
267
|
-
expect(data.auto_escalation).toBeDefined()
|
|
268
|
-
const ae = data.auto_escalation as Record<string, unknown>
|
|
269
|
-
expect(ae.reason).toBe('rate-limit')
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
interface CallLog {
|
|
273
|
-
pre: ToolCall[]
|
|
274
|
-
post: Array<{ call: ToolCall; result: ToolResult }>
|
|
275
|
-
dispatch: ToolCall[]
|
|
276
|
-
activity: Array<{ call: ToolCall; result: ToolResult }>
|
|
277
|
-
starts: ToolCall[]
|
|
278
|
-
ends: Array<{ call: ToolCall; result: ToolResult; durationMs: number }>
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const captureDeps = (
|
|
282
|
-
log: CallLog,
|
|
283
|
-
dispatchResult: ToolResult,
|
|
284
|
-
preShort?: ToolResult,
|
|
285
|
-
): EscalationDeps => ({
|
|
286
|
-
runPreCall: async call => {
|
|
287
|
-
log.pre.push(call)
|
|
288
|
-
return preShort ? { short: preShort } : {}
|
|
289
|
-
},
|
|
290
|
-
runPostCall: async (call, result) => {
|
|
291
|
-
log.post.push({ call, result })
|
|
292
|
-
},
|
|
293
|
-
dispatch: async call => {
|
|
294
|
-
log.dispatch.push(call)
|
|
295
|
-
return dispatchResult
|
|
296
|
-
},
|
|
297
|
-
appendActivity: async (call, result) => {
|
|
298
|
-
log.activity.push({ call, result })
|
|
299
|
-
},
|
|
300
|
-
onStart: call => {
|
|
301
|
-
log.starts.push(call)
|
|
302
|
-
},
|
|
303
|
-
onEnd: (call, result, durationMs) => {
|
|
304
|
-
log.ends.push({ call, result, durationMs })
|
|
305
|
-
},
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
test('runEscalation: passes through original when escalation not needed', async () => {
|
|
309
|
-
const log: CallLog = { pre: [], post: [], dispatch: [], activity: [], starts: [], ends: [] }
|
|
310
|
-
const original: ToolResult = { ok: true, data: { body: 'fine' } }
|
|
311
|
-
const out = await runEscalation({ needed: false }, original, captureDeps(log, { ok: true }))
|
|
312
|
-
expect(out).toBe(original)
|
|
313
|
-
expect(log.starts.length).toBe(0)
|
|
314
|
-
expect(log.dispatch.length).toBe(0)
|
|
315
|
-
})
|
|
316
|
-
|
|
317
|
-
test('runEscalation: ok escalation runs full pipeline + emits start/end + merges', async () => {
|
|
318
|
-
const log: CallLog = { pre: [], post: [], dispatch: [], activity: [], starts: [], ends: [] }
|
|
319
|
-
const original = blockedResult('https://x.com', 'cloudflare')
|
|
320
|
-
const escalation = escalationFor(fetchCall('c13', 'https://x.com'), 'cloudflare', 'https://x.com')
|
|
321
|
-
const escalated: ToolResult = { ok: true, data: { currentUrl: 'https://x.com' } }
|
|
322
|
-
const merged = await runEscalation(escalation, original, captureDeps(log, escalated))
|
|
323
|
-
expect(log.starts.length).toBe(1)
|
|
324
|
-
expect(log.starts[0]?.name).toBe('browser.navigate')
|
|
325
|
-
expect(log.pre.length).toBe(1)
|
|
326
|
-
expect(log.dispatch.length).toBe(1)
|
|
327
|
-
expect(log.post.length).toBe(1)
|
|
328
|
-
expect(log.activity.length).toBe(1)
|
|
329
|
-
expect(log.ends.length).toBe(1)
|
|
330
|
-
expect(typeof log.ends[0]?.durationMs).toBe('number')
|
|
331
|
-
expect(merged.ok).toBe(true)
|
|
332
|
-
const ae = (merged.data as Record<string, unknown>).auto_escalation as Record<string, unknown>
|
|
333
|
-
expect(ae.triggered).toBe(true)
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
test('runEscalation: pre-hook short-circuit skips dispatch + post-hook', async () => {
|
|
337
|
-
const log: CallLog = { pre: [], post: [], dispatch: [], activity: [], starts: [], ends: [] }
|
|
338
|
-
const original = blockedResult('https://x.com', 'cloudflare')
|
|
339
|
-
const escalation = escalationFor(fetchCall('c14', 'https://x.com'), 'cloudflare', 'https://x.com')
|
|
340
|
-
const denied: ToolResult = { ok: false, error: 'denied by approval' }
|
|
341
|
-
const merged = await runEscalation(escalation, original, captureDeps(log, { ok: true }, denied))
|
|
342
|
-
expect(log.dispatch.length).toBe(0)
|
|
343
|
-
expect(log.post.length).toBe(0)
|
|
344
|
-
expect(log.activity.length).toBe(1)
|
|
345
|
-
expect(log.activity[0]?.result.ok).toBe(false)
|
|
346
|
-
expect(merged.ok).toBe(false)
|
|
347
|
-
expect(merged.error).toContain('auto-escalation failed')
|
|
348
|
-
})
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { expect, test } from 'bun:test'
|
|
2
|
-
import { z } from 'zod'
|
|
3
|
-
import { ToolRegistry } from './registry'
|
|
4
|
-
|
|
5
|
-
test('register + find + dispatch a tool', async () => {
|
|
6
|
-
const reg = new ToolRegistry()
|
|
7
|
-
let captured: unknown = null
|
|
8
|
-
reg.register({
|
|
9
|
-
name: 'echo',
|
|
10
|
-
description: 'Echo back a message',
|
|
11
|
-
schema: z.object({ message: z.string() }),
|
|
12
|
-
handler: async args => {
|
|
13
|
-
captured = args
|
|
14
|
-
return { ok: true, data: args }
|
|
15
|
-
},
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
const r = await reg.dispatch({ id: 'c1', name: 'echo', args: { message: 'hi' } })
|
|
19
|
-
expect(r.ok).toBe(true)
|
|
20
|
-
expect(captured).toEqual({ message: 'hi' })
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
test('dispatch fails on unknown tool', async () => {
|
|
24
|
-
const reg = new ToolRegistry()
|
|
25
|
-
const r = await reg.dispatch({ id: 'c1', name: 'nope', args: {} })
|
|
26
|
-
expect(r.ok).toBe(false)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
test('dispatch fails on schema-invalid args', async () => {
|
|
30
|
-
const reg = new ToolRegistry()
|
|
31
|
-
reg.register({
|
|
32
|
-
name: 'echo',
|
|
33
|
-
description: 'Echo',
|
|
34
|
-
schema: z.object({ message: z.string() }),
|
|
35
|
-
handler: async () => ({ ok: true }),
|
|
36
|
-
})
|
|
37
|
-
const r = await reg.dispatch({ id: 'c1', name: 'echo', args: { message: 123 } })
|
|
38
|
-
expect(r.ok).toBe(false)
|
|
39
|
-
expect(r.error).toContain('Invalid args')
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
test('glob disable prevents dispatch', async () => {
|
|
43
|
-
const reg = new ToolRegistry({ 'defi.*': false })
|
|
44
|
-
reg.register({
|
|
45
|
-
name: 'defi.swap',
|
|
46
|
-
description: 'Swap',
|
|
47
|
-
schema: z.object({}),
|
|
48
|
-
handler: async () => ({ ok: true }),
|
|
49
|
-
})
|
|
50
|
-
expect(reg.find('defi.swap')).toBeUndefined()
|
|
51
|
-
const r = await reg.dispatch({ id: 'c1', name: 'defi.swap', args: {} })
|
|
52
|
-
expect(r.ok).toBe(false)
|
|
53
|
-
expect(r.error).toContain('Unknown tool')
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
test('schemas() emits OpenAI-compat shape', () => {
|
|
57
|
-
const reg = new ToolRegistry()
|
|
58
|
-
reg.register({
|
|
59
|
-
name: 'memory.save',
|
|
60
|
-
description: 'Save memory',
|
|
61
|
-
schema: z.object({ name: z.string(), type: z.enum(['feedback', 'user']) }),
|
|
62
|
-
handler: async () => ({ ok: true }),
|
|
63
|
-
})
|
|
64
|
-
const schemas = reg.schemas()
|
|
65
|
-
expect(schemas[0]!.type).toBe('function')
|
|
66
|
-
expect(schemas[0]!.function.name).toBe('memory.save')
|
|
67
|
-
const props = schemas[0]!.function.parameters.properties as Record<string, { type: string }>
|
|
68
|
-
expect(props.name).toEqual({ type: 'string' })
|
|
69
|
-
expect(schemas[0]!.function.parameters.required).toEqual(['name', 'type'])
|
|
70
|
-
})
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'bun:test'
|
|
2
|
-
import { z } from 'zod'
|
|
3
|
-
import { coerceBool, coerceInt } from './zod-helpers'
|
|
4
|
-
import { zodToJsonSchema } from './zod-schema'
|
|
5
|
-
|
|
6
|
-
describe('coerceBool', () => {
|
|
7
|
-
it('accepts native booleans', () => {
|
|
8
|
-
expect(coerceBool.parse(true)).toBe(true)
|
|
9
|
-
expect(coerceBool.parse(false)).toBe(false)
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('coerces "true"/"false" strings (qwen3.6-plus quirk)', () => {
|
|
13
|
-
expect(coerceBool.parse('true')).toBe(true)
|
|
14
|
-
expect(coerceBool.parse('True')).toBe(true)
|
|
15
|
-
expect(coerceBool.parse('false')).toBe(false)
|
|
16
|
-
expect(coerceBool.parse('False')).toBe(false)
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('coerces 0/1 + yes/no', () => {
|
|
20
|
-
expect(coerceBool.parse(1)).toBe(true)
|
|
21
|
-
expect(coerceBool.parse(0)).toBe(false)
|
|
22
|
-
expect(coerceBool.parse('yes')).toBe(true)
|
|
23
|
-
expect(coerceBool.parse('no')).toBe(false)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('rejects garbage', () => {
|
|
27
|
-
expect(() => coerceBool.parse('maybe')).toThrow()
|
|
28
|
-
expect(() => coerceBool.parse({})).toThrow()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('zodToJsonSchema unwraps to type:boolean', () => {
|
|
32
|
-
const schema = z.object({ flag: coerceBool.optional() })
|
|
33
|
-
const json = zodToJsonSchema(schema)
|
|
34
|
-
expect((json.properties.flag as { type: string }).type).toBe('boolean')
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
describe('coerceInt', () => {
|
|
39
|
-
it('accepts native integers', () => {
|
|
40
|
-
expect(coerceInt.parse(0)).toBe(0)
|
|
41
|
-
expect(coerceInt.parse(42)).toBe(42)
|
|
42
|
-
expect(coerceInt.parse(-7)).toBe(-7)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('coerces "400"/"42" strings (qwen3.6-plus numeric quirk)', () => {
|
|
46
|
-
expect(coerceInt.parse('400')).toBe(400)
|
|
47
|
-
expect(coerceInt.parse(' 42 ')).toBe(42)
|
|
48
|
-
expect(coerceInt.parse('-7')).toBe(-7)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('rejects floats + garbage strings', () => {
|
|
52
|
-
expect(() => coerceInt.parse('1.5')).toThrow()
|
|
53
|
-
expect(() => coerceInt.parse('abc')).toThrow()
|
|
54
|
-
expect(() => coerceInt.parse('')).toThrow()
|
|
55
|
-
expect(() => coerceInt.parse({})).toThrow()
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('zodToJsonSchema unwraps to type:number', () => {
|
|
59
|
-
const schema = z.object({ count: coerceInt.optional() })
|
|
60
|
-
const json = zodToJsonSchema(schema)
|
|
61
|
-
expect((json.properties.count as { type: string }).type).toBe('number')
|
|
62
|
-
})
|
|
63
|
-
})
|
package/src/wallet/drain.test.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import { SWEEP_GAS_RESERVE_MIST, computeSweepAmount } from './drain'
|
|
3
|
-
|
|
4
|
-
const AGENT = `0x${'1'.repeat(64)}`
|
|
5
|
-
/** 1 SUI in MIST. */
|
|
6
|
-
const sui = (n: number): bigint => BigInt(Math.round(n * 1e9))
|
|
7
|
-
|
|
8
|
-
describe('computeSweepAmount', () => {
|
|
9
|
-
test('subtracts default gas reserve from balance when comfortable', () => {
|
|
10
|
-
const balanceMist = sui(0.1)
|
|
11
|
-
const r = computeSweepAmount({ balanceMist, agentAddress: AGENT })
|
|
12
|
-
expect(r.error).toBeUndefined()
|
|
13
|
-
expect(r.gasReserve).toBe(SWEEP_GAS_RESERVE_MIST)
|
|
14
|
-
expect(r.value).toBe(balanceMist - SWEEP_GAS_RESERVE_MIST)
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test('returns error string when balance below reserve', () => {
|
|
18
|
-
const balanceMist = SWEEP_GAS_RESERVE_MIST
|
|
19
|
-
const r = computeSweepAmount({ balanceMist, agentAddress: AGENT })
|
|
20
|
-
expect(r.value).toBe(0n)
|
|
21
|
-
expect(r.error).toContain('below gas reserve')
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
test('honors gasReserveOverride', () => {
|
|
25
|
-
const balanceMist = sui(1)
|
|
26
|
-
const override = sui(0.005)
|
|
27
|
-
const r = computeSweepAmount({
|
|
28
|
-
balanceMist,
|
|
29
|
-
agentAddress: AGENT,
|
|
30
|
-
gasReserveOverride: override,
|
|
31
|
-
})
|
|
32
|
-
expect(r.gasReserve).toBe(override)
|
|
33
|
-
expect(r.value).toBe(balanceMist - override)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
test('error wording surfaces the agent address + balance + reserve', () => {
|
|
37
|
-
const r = computeSweepAmount({ balanceMist: 0n, agentAddress: AGENT })
|
|
38
|
-
expect(r.error).toContain(AGENT)
|
|
39
|
-
expect(r.error).toContain('0.000000 SUI')
|
|
40
|
-
})
|
|
41
|
-
})
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { expect, test } from 'bun:test'
|
|
2
|
-
import { randomBytes } from 'node:crypto'
|
|
3
|
-
import { decryptKey, encryptKey } from './keystore'
|
|
4
|
-
|
|
5
|
-
test('encrypt + decrypt round-trip', () => {
|
|
6
|
-
const pk = new Uint8Array(randomBytes(32))
|
|
7
|
-
const encrypted = encryptKey(pk, 'test-passphrase')
|
|
8
|
-
expect(encrypted.version).toBe(1)
|
|
9
|
-
const decrypted = decryptKey(encrypted, 'test-passphrase')
|
|
10
|
-
expect(Buffer.from(decrypted).toString('hex')).toBe(Buffer.from(pk).toString('hex'))
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
test('wrong passphrase fails', () => {
|
|
14
|
-
const pk = new Uint8Array(32).fill(0xab)
|
|
15
|
-
const encrypted = encryptKey(pk, 'right')
|
|
16
|
-
expect(() => decryptKey(encrypted, 'wrong')).toThrow()
|
|
17
|
-
})
|