@kaleidorg/mind 0.2.0 → 0.3.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/dist/capabilities.d.ts +4 -0
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +7 -0
- package/dist/capabilities.js.map +1 -1
- package/dist/engine.d.ts +9 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +1 -0
- package/dist/engine.js.map +1 -1
- package/dist/funnel.d.ts +6 -0
- package/dist/funnel.d.ts.map +1 -1
- package/dist/funnel.js +26 -6
- package/dist/funnel.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/kaleidoswap/contract.d.ts +72 -0
- package/dist/kaleidoswap/contract.d.ts.map +1 -0
- package/dist/kaleidoswap/contract.js +125 -0
- package/dist/kaleidoswap/contract.js.map +1 -0
- package/dist/knowledge/btc-map.d.ts +87 -0
- package/dist/knowledge/btc-map.d.ts.map +1 -0
- package/dist/knowledge/btc-map.js +365 -0
- package/dist/knowledge/btc-map.js.map +1 -0
- package/dist/lsps1/contract.d.ts +55 -0
- package/dist/lsps1/contract.d.ts.map +1 -0
- package/dist/lsps1/contract.js +91 -0
- package/dist/lsps1/contract.js.map +1 -0
- package/dist/memory/store.d.ts +7 -1
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +43 -3
- package/dist/memory/store.js.map +1 -1
- package/dist/memory/types.d.ts +12 -0
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/recipe/kaleidoswap-atomic.d.ts +27 -0
- package/dist/recipe/kaleidoswap-atomic.d.ts.map +1 -0
- package/dist/recipe/kaleidoswap-atomic.js +111 -0
- package/dist/recipe/kaleidoswap-atomic.js.map +1 -0
- package/dist/recipe/runner.d.ts.map +1 -1
- package/dist/recipe/runner.js +13 -1
- package/dist/recipe/runner.js.map +1 -1
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js +20 -2
- package/dist/skills/registry.js.map +1 -1
- package/dist/wallet/confirm.d.ts +12 -0
- package/dist/wallet/confirm.d.ts.map +1 -0
- package/dist/wallet/confirm.js +67 -0
- package/dist/wallet/confirm.js.map +1 -0
- package/package.json +2 -1
- package/skills/README.md +6 -1
- package/skills/kaleido-lsps/SKILL.md +56 -0
- package/skills/kaleido-trading/SKILL.md +85 -18
- package/skills/merchant-finder/SKILL.md +87 -0
- package/skills/paid-data/SKILL.md +12 -0
- package/skills/wallet-assistant/SKILL.md +38 -0
- package/src/capabilities.ts +12 -0
- package/src/context/context.test.ts +6 -2
- package/src/engine.ts +6 -0
- package/src/funnel.ts +32 -7
- package/src/index.ts +43 -0
- package/src/kaleidoswap/contract.test.ts +147 -0
- package/src/kaleidoswap/contract.ts +212 -0
- package/src/knowledge/btc-map.test.ts +188 -0
- package/src/knowledge/btc-map.ts +446 -0
- package/src/lsps1/contract.test.ts +81 -0
- package/src/lsps1/contract.ts +132 -0
- package/src/memory/memory.test.ts +55 -0
- package/src/memory/store.ts +49 -4
- package/src/memory/types.ts +13 -0
- package/src/recipe/kaleidoswap-atomic.test.ts +138 -0
- package/src/recipe/kaleidoswap-atomic.ts +117 -0
- package/src/recipe/runner.ts +13 -1
- package/src/skills/registry.ts +21 -2
- package/src/skills/skills.test.ts +42 -0
- package/src/wallet/confirm.test.ts +57 -0
- package/src/wallet/confirm.ts +74 -0
- package/skills/kaleido-wallet/SKILL.md +0 -28
|
@@ -1,31 +1,98 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: kaleido-trading
|
|
3
|
-
description: "Trade
|
|
4
|
-
tools: get_price,
|
|
5
|
-
triggers:
|
|
3
|
+
description: "Trade on KaleidoSwap — quote and execute swaps between BTC and RGB assets (USDT, XAUT). Get assets and pairs, pull an executable quote, place a market order, or track an atomic swap end-to-end. Triggers when the user wants a price, a quote, to swap or trade assets, or to rebalance between BTC and stablecoins."
|
|
4
|
+
tools: get_price, fiat_to_sats, kaleidoswap_get_assets, kaleidoswap_get_pairs, kaleidoswap_get_quote, kaleidoswap_get_nodeinfo, kaleidoswap_place_order, kaleidoswap_get_order_status, kaleidoswap_get_order_history
|
|
5
|
+
triggers: quote, swap, trade, rebalance, slippage, pair, pairs, usdt, xaut, kaleidoswap, rfq
|
|
6
6
|
metadata:
|
|
7
7
|
author: kaleidoswap
|
|
8
|
-
version: "0.
|
|
9
|
-
surface: "kaleido-mcp (KaleidoSwap maker + market data)"
|
|
8
|
+
version: "0.3.0"
|
|
10
9
|
---
|
|
11
10
|
|
|
12
11
|
# KaleidoSwap trading
|
|
13
12
|
|
|
14
|
-
Quote
|
|
15
|
-
|
|
13
|
+
Quote and execute swaps on the KaleidoSwap maker. The model picks tools by
|
|
14
|
+
name; the host binds them through whichever transport it runs over (WDK on
|
|
15
|
+
mobile, HTTP/MCP/CLI on desktop).
|
|
16
|
+
|
|
17
|
+
## Critical rules — these override everything else
|
|
18
|
+
|
|
19
|
+
You have **no knowledge** of any price, quote, fee, pair, or order. Every
|
|
20
|
+
number, pair, or quote id in your reply MUST come from a tool result returned
|
|
21
|
+
in the CURRENT turn:
|
|
22
|
+
|
|
23
|
+
- "What's the BTC price?" → call `get_price` and state the number it returns.
|
|
24
|
+
- "What pairs are listed?" → call `kaleidoswap_get_pairs` and list them.
|
|
25
|
+
- "Quote 100k sats to USDT" → call `kaleidoswap_get_quote(BTC, USDT, 100000)`,
|
|
26
|
+
then state the receive amount + fees from the result.
|
|
27
|
+
|
|
28
|
+
**Calling the tool IS the answer.** Never write "the pairs are listed using
|
|
29
|
+
kaleidoswap_get_pairs" or "the function returns the quote" — just call it.
|
|
30
|
+
|
|
31
|
+
**Never reuse a number across turns.** If the user asks a new question, the
|
|
32
|
+
previous turn's quote, price, or fee is irrelevant — fetch fresh.
|
|
33
|
+
|
|
34
|
+
**Never invent a quote.** Without a `quote_id` and `receive_amount` returned
|
|
35
|
+
this turn, you do not have a quote. Say so and re-quote.
|
|
36
|
+
|
|
37
|
+
## Asset codes (canonical)
|
|
38
|
+
|
|
39
|
+
Only these codes are accepted:
|
|
40
|
+
|
|
41
|
+
- `BTC` (Bitcoin, amounts always in satoshis)
|
|
42
|
+
- `USDT` (Tether) — **not** `USD`, **not** `tether`
|
|
43
|
+
- `XAUT` (Tether Gold) — **not** `XAU`, **not** `gold`
|
|
44
|
+
|
|
45
|
+
When the user types `USD` they almost always mean `USDT` — confirm before
|
|
46
|
+
quoting. Same for `gold` → `XAUT`. Don't silently substitute.
|
|
47
|
+
|
|
48
|
+
## Tools
|
|
49
|
+
|
|
50
|
+
### `kaleidoswap_get_pairs` — no args
|
|
51
|
+
Use when the user asks "what can I trade", "list pairs", "what's available",
|
|
52
|
+
or before quoting an unfamiliar pair.
|
|
53
|
+
|
|
54
|
+
### `kaleidoswap_get_quote` — REQUIRES `amount`
|
|
55
|
+
Required args: `from_asset` AND `to_asset` AND `amount`. The maker rejects
|
|
56
|
+
calls missing any of these.
|
|
57
|
+
|
|
58
|
+
**If the user didn't give an amount, ASK for it. Do not call the tool with
|
|
59
|
+
from/to alone.**
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
- "Quote 100k sats to USDT" → `{from_asset: "BTC", to_asset: "USDT", amount: 100000}`
|
|
63
|
+
- "What's the USDT/BTC rate?" → ask: "How many USDT do you want to swap?"
|
|
64
|
+
(no amount → no quote possible).
|
|
65
|
+
- "Buy 50 USDT of BTC" → `{from_asset: "USDT", to_asset: "BTC", amount: 50}`
|
|
66
|
+
(USDT is what's being spent).
|
|
67
|
+
|
|
68
|
+
### `kaleidoswap_place_order(quote_id)` 🔒 spend
|
|
69
|
+
Only after `kaleidoswap_get_quote` returned a `quote_id` THIS turn, and only
|
|
70
|
+
when the user has explicitly approved the amount + direction.
|
|
71
|
+
|
|
72
|
+
### `kaleidoswap_get_order_status(order_id)`
|
|
73
|
+
Poll after placing an order. Report status plainly — pending, settling,
|
|
74
|
+
completed, failed.
|
|
16
75
|
|
|
17
76
|
## Flow
|
|
18
77
|
|
|
19
|
-
1. **
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
78
|
+
1. **Pick a pair** — skip when obvious (`BTC/USDT`, `BTC/XAUT`).
|
|
79
|
+
2. **Quote** — `kaleidoswap_get_quote`. REQUIRES amount.
|
|
80
|
+
3. **Show + confirm** — surface pair, direction, amount in, expected out,
|
|
81
|
+
fees, slippage. **Never hide cost** — a small model must not abbreviate
|
|
82
|
+
fees out of the message.
|
|
83
|
+
4. **Place** — spend-gated by the engine. The host pauses for the user.
|
|
84
|
+
5. **Track** — poll `kaleidoswap_get_order_status` until it terminates.
|
|
85
|
+
|
|
86
|
+
## Don'ts
|
|
27
87
|
|
|
28
|
-
|
|
88
|
+
- Don't invent prices, quotes, quote_ids, or order_ids.
|
|
89
|
+
- Don't reuse a number from a previous turn.
|
|
90
|
+
- Don't describe how a tool works — call it.
|
|
91
|
+
- Don't call `kaleidoswap_get_quote` with from/to only — ask for the amount.
|
|
92
|
+
- Don't accept `XAU` as `XAUT` or `USD` as `USDT` silently — confirm.
|
|
93
|
+
- Don't retry the same failing tool call in a loop. If a call fails, read the
|
|
94
|
+
error and either ask the user, fix the args, or stop.
|
|
29
95
|
|
|
30
|
-
|
|
31
|
-
|
|
96
|
+
For the atomic-swap flow (trust-minimised cross-asset swap that the maker
|
|
97
|
+
can't settle from balance), use the `kaleido-trading` atomic recipe — the
|
|
98
|
+
agentic chain is not safe to plan on a 0.6B model.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: merchant-finder
|
|
3
|
+
description: "Find Bitcoin-accepting merchants near the user using live BTC Map data and the device's real location. Triggers when the user asks where to spend Bitcoin, for a shop, store, restaurant, cafe, bar, or ATM that accepts Bitcoin, or for merchants nearby."
|
|
4
|
+
tools: find_merchant_locations, get_merchant_info
|
|
5
|
+
triggers: merchant, merchants, shop, shops, store, stores, restaurant, restaurants, cafe, cafes, bar, bars, atm, atms, accept, accepts, accepting, nearby, near me, around, place, places, spend, find, pizza, food, coffee, bitcoin map, btcmap
|
|
6
|
+
metadata:
|
|
7
|
+
author: kaleidoswap
|
|
8
|
+
version: "0.2.0"
|
|
9
|
+
homepage: "https://btcmap.org"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Merchant finder
|
|
13
|
+
|
|
14
|
+
Discover places that accept Bitcoin payments — cafés, restaurants, bars, shops,
|
|
15
|
+
and ATMs. Live BTC Map data when the host injects a fetcher + location;
|
|
16
|
+
otherwise a small offline list keeps the skill answerable.
|
|
17
|
+
|
|
18
|
+
## Critical rule — never answer from memory
|
|
19
|
+
|
|
20
|
+
You have **no knowledge of any merchant**. Every place, name, address and
|
|
21
|
+
distance in your reply MUST come from a `find_merchant_locations` result
|
|
22
|
+
returned in the CURRENT turn. **Call `find_merchant_locations` for every
|
|
23
|
+
place question**, even if a similar question was answered earlier — do NOT
|
|
24
|
+
reuse or adapt a previous answer. Never invent a merchant.
|
|
25
|
+
|
|
26
|
+
## How to call the tools
|
|
27
|
+
|
|
28
|
+
1. **Start with `find_merchant_locations`.** Pass ONLY the fields the user
|
|
29
|
+
actually named — do not invent constraints:
|
|
30
|
+
|
|
31
|
+
- `query` — a specific thing the user named (e.g. `"tapas"`, `"coffee"`,
|
|
32
|
+
`"pizza"`). Omit when they only said "near me" or only named a place.
|
|
33
|
+
|
|
34
|
+
- `category` — must be EXACTLY one of: `restaurant`, `cafe`, `bar`, `shop`,
|
|
35
|
+
`grocery`, `lodging`, `atm`. **Anything else is invalid — leave it empty.**
|
|
36
|
+
The words "merchant", "merchants", "place", "places", "store", "stores"
|
|
37
|
+
are NOT categories — they're the generic noun for what you're searching
|
|
38
|
+
for, so they belong in `query` at best, never in `category`.
|
|
39
|
+
|
|
40
|
+
- `near_address` — a city, neighbourhood, or address (e.g. `"Milan"`,
|
|
41
|
+
`"Bitcoin Beach, El Salvador"`). Use this any time the user names a
|
|
42
|
+
location instead of "near me".
|
|
43
|
+
|
|
44
|
+
- `radius_km` — **omit unless the user names a specific number.** The
|
|
45
|
+
default (5 km) is a sensible search radius for a city. Don't pick a
|
|
46
|
+
small radius (1, 2, 3) yourself — city-wide searches need 5+.
|
|
47
|
+
|
|
48
|
+
- `limit` — 1–20, default 10. Omit unless the user names a count.
|
|
49
|
+
|
|
50
|
+
Examples (positive):
|
|
51
|
+
- "where can I spend btc near me" → `find_merchant_locations({})`
|
|
52
|
+
- "find merchants in Milan" → `find_merchant_locations({ near_address: "Milan" })`
|
|
53
|
+
↑ no `category` — "merchants" is NOT a category.
|
|
54
|
+
- "cafes in Lisbon" → `find_merchant_locations({ category: "cafe", near_address: "Lisbon" })`
|
|
55
|
+
- "pizza places in Switzerland that take bitcoin" →
|
|
56
|
+
`find_merchant_locations({ query: "pizza", near_address: "Switzerland" })`
|
|
57
|
+
↑ "places" is NOT a category — pizza goes in `query`.
|
|
58
|
+
- "lightning bars in NYC, within 2 km" →
|
|
59
|
+
`find_merchant_locations({ category: "bar", near_address: "New York", radius_km: 2 })`
|
|
60
|
+
↑ user explicitly said "2 km" → set radius_km.
|
|
61
|
+
|
|
62
|
+
Examples (anti — do NOT do these):
|
|
63
|
+
- ❌ `category: "merchant"` (not a category)
|
|
64
|
+
- ❌ `category: "place"` (not a category)
|
|
65
|
+
- ❌ `radius_km: 2` when the user didn't say "2 km" — you're picking a
|
|
66
|
+
too-small radius and the result will be empty.
|
|
67
|
+
|
|
68
|
+
2. **Present the results.** Each row carries:
|
|
69
|
+
- `name`, `category`, `address`
|
|
70
|
+
- `distance_m` when present — show in metres or km
|
|
71
|
+
- `accepts_bitcoin` / `accepts_lightning` — relevant because Lightning is
|
|
72
|
+
fastest for small payments
|
|
73
|
+
- `phone`, `website`, `opening_hours` when present — surface if asked
|
|
74
|
+
|
|
75
|
+
3. **Use `get_merchant_info` only when** the user asks for more detail on one
|
|
76
|
+
specific result. Pass `merchant_id` (preferred) or `merchant_name`.
|
|
77
|
+
|
|
78
|
+
## Reply style
|
|
79
|
+
|
|
80
|
+
- Be concise. One line per merchant works:
|
|
81
|
+
`Name — category, address (X m away, accepts: lightning, onchain)`.
|
|
82
|
+
- If the result `source` is `offline`, say so plainly — it means the live
|
|
83
|
+
BTC Map fetch wasn't available, so the list is limited.
|
|
84
|
+
- If `find_merchant_locations` returns zero merchants, say so — don't invent
|
|
85
|
+
places. Suggest widening `radius_km` or trying `near_address`.
|
|
86
|
+
- When the user says "near me" and `precise_location` is false, mention which
|
|
87
|
+
fallback location was used so they know it's not their actual GPS.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: paid-data
|
|
3
|
+
description: Fetch premium or paywalled data that requires a small Lightning (L402) payment — paid feeds, gated APIs, unlockable resources. Triggers when the user wants premium, paid, or unlockable data behind an L402 paywall.
|
|
4
|
+
tools: fetch_paid_resource
|
|
5
|
+
triggers: premium, paid, l402, feed, subscription, unlock, paywall
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Paid data
|
|
9
|
+
|
|
10
|
+
Fetch L402-paywalled resources, paying small Lightning invoices automatically
|
|
11
|
+
with `fetch_paid_resource`. Small amounts pay without prompting (capped);
|
|
12
|
+
anything larger is declined. Tell the user what was paid and what was returned.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wallet-assistant
|
|
3
|
+
description: Everyday wallet tasks on this phone — check the BTC/asset balance, create an invoice to receive, send a payment, look up a contact, get the BTC price, or convert a fiat amount to sats. Triggers when the user asks about their balance, wants to receive or send money, pay an invoice, or pay a contact.
|
|
4
|
+
tools: get_balances, get_price, fiat_to_sats, resolve_contact, send_payment, rln_pay_invoice, rln_create_ln_invoice, spark_create_invoice
|
|
5
|
+
triggers: balance, pay, send, receive, address, invoice, transactions, contact, funds, money, price, sats, eur, gbp
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Wallet assistant
|
|
9
|
+
|
|
10
|
+
You operate the user's on-device multi-L2 Bitcoin wallet. ALWAYS use a tool to
|
|
11
|
+
get real data — NEVER invent a balance, address, amount, price, or result.
|
|
12
|
+
|
|
13
|
+
## Critical rules
|
|
14
|
+
|
|
15
|
+
You have no knowledge of balances, prices, addresses, or invoices. Every value
|
|
16
|
+
in your reply MUST come from a tool result returned in the CURRENT turn — do
|
|
17
|
+
not reuse a number from a previous turn.
|
|
18
|
+
|
|
19
|
+
When a tool returns multiple fields, **report all the load-bearing ones**:
|
|
20
|
+
- `get_balances` may return `{confirmed, pending, total}` — when `pending`
|
|
21
|
+
is non-zero, report BOTH. `confirmed` is spendable; `pending` is settling
|
|
22
|
+
and is NOT spendable yet. The user needs to know the difference.
|
|
23
|
+
- `fiat_to_sats` returns `{sats}` plus a `note` when the currency was an
|
|
24
|
+
approximation — surface the note.
|
|
25
|
+
|
|
26
|
+
## Rules
|
|
27
|
+
|
|
28
|
+
- Balance / "how much do I have" → call `get_balances`, then state the number.
|
|
29
|
+
- Receive / "an invoice for N sats" → call `rln_create_ln_invoice` (or
|
|
30
|
+
`spark_create_invoice`) with the amount.
|
|
31
|
+
- Price → `get_price`. "How many sats is 3 EUR" → `fiat_to_sats`.
|
|
32
|
+
- Pay a Lightning invoice → `rln_pay_invoice`.
|
|
33
|
+
- Send to a person/amount → first `resolve_contact` (and `fiat_to_sats` if the
|
|
34
|
+
amount is in fiat), then `send_payment` with the amount in sats and the
|
|
35
|
+
recipient. State the amount and destination; the app asks the user to confirm
|
|
36
|
+
before it sends.
|
|
37
|
+
|
|
38
|
+
Keep replies short, but never drop a balance component or a fee.
|
package/src/capabilities.ts
CHANGED
|
@@ -25,6 +25,10 @@ export interface MindCapabilities {
|
|
|
25
25
|
memory: boolean;
|
|
26
26
|
/** Semantic recall for memory (needs embeddings). */
|
|
27
27
|
semanticMemory: boolean;
|
|
28
|
+
/** Embedding-only dedup of near-duplicate memories (zero inference — mobile-safe). */
|
|
29
|
+
dedupeMemory: boolean;
|
|
30
|
+
/** LLM merge of near-duplicate memories (an extra inference — capable/delegated only). */
|
|
31
|
+
mergeMemory: boolean;
|
|
28
32
|
/** Retrieval-augmented generation (needs embeddings + enough RAM/context). */
|
|
29
33
|
rag: boolean;
|
|
30
34
|
/** Token budget for injected system context. */
|
|
@@ -48,6 +52,12 @@ export function capabilityProfile(input: CapabilityInput): MindCapabilities {
|
|
|
48
52
|
const memory = budget >= 256;
|
|
49
53
|
const semanticMemory = memory && hasEmb;
|
|
50
54
|
|
|
55
|
+
// Consolidation: embedding-only dedup is cheap (no inference) — on wherever
|
|
56
|
+
// semantic memory is. The LLM merge costs an extra inference, so reserve it
|
|
57
|
+
// for delegated or roomy on-device setups; never run it on a tiny phone model.
|
|
58
|
+
const dedupeMemory = semanticMemory;
|
|
59
|
+
const mergeMemory = dedupeMemory && (input.delegated || (ramGb >= 4 && ctx >= 4096));
|
|
60
|
+
|
|
51
61
|
// RAG is the expensive one: needs embeddings, a non-tiny context window, and
|
|
52
62
|
// (on-device) enough RAM to hold an embedding model + index.
|
|
53
63
|
const rag = hasEmb && ctx >= 4096 && (input.delegated || ramGb >= 3);
|
|
@@ -59,6 +69,8 @@ export function capabilityProfile(input: CapabilityInput): MindCapabilities {
|
|
|
59
69
|
return {
|
|
60
70
|
memory,
|
|
61
71
|
semanticMemory,
|
|
72
|
+
dedupeMemory,
|
|
73
|
+
mergeMemory,
|
|
62
74
|
rag,
|
|
63
75
|
contextBudgetTokens: budget,
|
|
64
76
|
topKMemory,
|
|
@@ -60,18 +60,22 @@ describe('ContextBuilder', () => {
|
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
describe('capabilityProfile', () => {
|
|
63
|
-
it('low-end phone: memory yes, RAG no', () => {
|
|
63
|
+
it('low-end phone: memory yes, RAG no, dedup yes but no LLM merge', () => {
|
|
64
64
|
const c = capabilityProfile({ ramBytes: 2 * 1024 ** 3, modelCtxTokens: 2048, hasEmbeddings: true });
|
|
65
65
|
expect(c.memory).toBe(true);
|
|
66
66
|
expect(c.rag).toBe(false); // ctx too small + low RAM
|
|
67
67
|
expect(c.topKRag).toBe(0);
|
|
68
|
+
expect(c.dedupeMemory).toBe(true); // embedding-only dedup is mobile-safe
|
|
69
|
+
expect(c.mergeMemory).toBe(false); // never run merge inference on a tiny phone
|
|
68
70
|
});
|
|
69
71
|
|
|
70
|
-
it('desktop / delegated: RAG on', () => {
|
|
72
|
+
it('desktop / delegated: RAG on, memory merge on', () => {
|
|
71
73
|
const c = capabilityProfile({ modelCtxTokens: 8192, hasEmbeddings: true, delegated: true });
|
|
72
74
|
expect(c.rag).toBe(true);
|
|
73
75
|
expect(c.topKRag).toBeGreaterThan(0);
|
|
74
76
|
expect(c.semanticMemory).toBe(true);
|
|
77
|
+
expect(c.dedupeMemory).toBe(true);
|
|
78
|
+
expect(c.mergeMemory).toBe(true);
|
|
75
79
|
});
|
|
76
80
|
|
|
77
81
|
it('no embeddings → no RAG, no semantic memory', () => {
|
package/src/engine.ts
CHANGED
|
@@ -35,6 +35,11 @@ export interface AgenticOptions {
|
|
|
35
35
|
onStart?: (requestId: string, turn: number) => void;
|
|
36
36
|
/** Fired when the model requests a tool, before it executes. */
|
|
37
37
|
onToolCall?: (call: { name: string; arguments: Record<string, unknown> }, turn: number) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Fired after a tool returns (success OR error — errors arrive as `{error}`).
|
|
40
|
+
* Useful for surfacing the raw response back to the user in a debug UI.
|
|
41
|
+
*/
|
|
42
|
+
onToolResult?: (event: { name: string; arguments: Record<string, unknown>; result: unknown }, turn: number) => void;
|
|
38
43
|
/** Human-in-the-loop gate for tools flagged requiresConfirmation. */
|
|
39
44
|
onConfirm?: (call: { name: string; arguments: Record<string, unknown> }) => Promise<ConfirmDecision>;
|
|
40
45
|
/**
|
|
@@ -127,6 +132,7 @@ export class Engine {
|
|
|
127
132
|
}
|
|
128
133
|
|
|
129
134
|
executed.push({ name: call.name, arguments: call.arguments, result });
|
|
135
|
+
opts.onToolResult?.({ name: call.name, arguments: call.arguments, result }, turn);
|
|
130
136
|
history.push({
|
|
131
137
|
role: 'tool',
|
|
132
138
|
content: typeof result === 'string' ? result : JSON.stringify(result),
|
package/src/funnel.ts
CHANGED
|
@@ -34,13 +34,31 @@ import type { LLMProvider } from './providers/types.js';
|
|
|
34
34
|
import type { ConfirmDecision, Message, ToolResult } from './types.js';
|
|
35
35
|
|
|
36
36
|
/** Base system prompt for the wallet assistant. Hosts may override. */
|
|
37
|
-
export const DEFAULT_WALLET_SYSTEM =
|
|
38
|
-
'You are KaleidoSwap, a concise, privacy-first assistant running inside a
|
|
39
|
-
'non-custodial Bitcoin, Lightning and RGB wallet.
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
export const DEFAULT_WALLET_SYSTEM = [
|
|
38
|
+
'You are KaleidoSwap, a concise, privacy-first assistant running inside a',
|
|
39
|
+
'non-custodial Bitcoin, Lightning and RGB wallet.',
|
|
40
|
+
'',
|
|
41
|
+
'CORE RULES (these override every skill instruction):',
|
|
42
|
+
"1. If a tool can answer the user's question, CALL IT. Never describe how a",
|
|
43
|
+
" tool works (\"the pairs are listed using kaleidoswap_get_pairs\") — calling",
|
|
44
|
+
' the tool IS the answer.',
|
|
45
|
+
'2. Never invent a balance, address, amount, price, quote, fee, pair, or any',
|
|
46
|
+
" other value. Every number or identifier in your reply MUST come from a tool",
|
|
47
|
+
' result returned in the CURRENT turn.',
|
|
48
|
+
'3. Never reuse a number, name, or detail from a previous turn unless the user',
|
|
49
|
+
' is explicitly asking about that earlier result. Each new question gets a',
|
|
50
|
+
' fresh tool call.',
|
|
51
|
+
'4. If a tool needs a required argument the user did not give (e.g. an amount',
|
|
52
|
+
" for a quote), ASK for it. Do not invent values. Do not call the tool with",
|
|
53
|
+
' the required field missing.',
|
|
54
|
+
'5. All BTC amounts are in satoshis. Asset codes are case-insensitive but the',
|
|
55
|
+
' canonical forms are BTC, USDT, XAUT — do not silently shorten to USD, XAU.',
|
|
56
|
+
'',
|
|
57
|
+
'Keep replies short and friendly. When a tool returns multiple fields, surface',
|
|
58
|
+
"the ones that matter — never collapse a structured result to a single number",
|
|
59
|
+
'when other fields are non-zero or safety-relevant (e.g. pending balances,',
|
|
60
|
+
'fees, slippage).',
|
|
61
|
+
].join('\n');
|
|
44
62
|
|
|
45
63
|
/** Tools that stay available even when a skill narrows the set. */
|
|
46
64
|
const AMBIENT_MEMORY = ['remember', 'recall'];
|
|
@@ -74,6 +92,12 @@ export interface FunnelCallbacks {
|
|
|
74
92
|
call: { name: string; arguments: Record<string, unknown> },
|
|
75
93
|
info: { requiresConfirmation: boolean },
|
|
76
94
|
) => void;
|
|
95
|
+
/** A tool returned a result (agentic tier). Errors arrive as `{error}`. */
|
|
96
|
+
onToolResult?: (event: {
|
|
97
|
+
name: string;
|
|
98
|
+
arguments: Record<string, unknown>;
|
|
99
|
+
result: unknown;
|
|
100
|
+
}) => void;
|
|
77
101
|
onConfirm?: (call: { name: string; arguments: Record<string, unknown> }) => Promise<ConfirmDecision>;
|
|
78
102
|
}
|
|
79
103
|
|
|
@@ -253,6 +277,7 @@ export class Funnel {
|
|
|
253
277
|
.then((def) => cbs.onToolCall?.(call, { requiresConfirmation: !!def?.requiresConfirmation }))
|
|
254
278
|
.catch(() => cbs.onToolCall?.(call, { requiresConfirmation: false }));
|
|
255
279
|
},
|
|
280
|
+
onToolResult: cbs.onToolResult,
|
|
256
281
|
onConfirm: cbs.onConfirm,
|
|
257
282
|
});
|
|
258
283
|
return { text: res.text ?? '', tier: 'agentic', toolCalls: res.toolCalls, turns: res.turns };
|
package/src/index.ts
CHANGED
|
@@ -49,6 +49,40 @@ export type {
|
|
|
49
49
|
WalletHandler,
|
|
50
50
|
BindWalletOptions,
|
|
51
51
|
} from './wallet/contract.js';
|
|
52
|
+
export { confirmReadback } from './wallet/confirm.js';
|
|
53
|
+
|
|
54
|
+
// ── KaleidoSwap maker tool contract (single source of truth) ────────────────
|
|
55
|
+
export {
|
|
56
|
+
KALEIDOSWAP_TOOLS,
|
|
57
|
+
KALEIDOSWAP_SPEND_TOOLS,
|
|
58
|
+
isKaleidoswapSpendTool,
|
|
59
|
+
getKaleidoswapTool,
|
|
60
|
+
kaleidoswapTools,
|
|
61
|
+
bindKaleidoswapTools,
|
|
62
|
+
} from './kaleidoswap/contract.js';
|
|
63
|
+
export type {
|
|
64
|
+
KaleidoswapGroup,
|
|
65
|
+
KaleidoswapToolDef,
|
|
66
|
+
KaleidoswapHandler,
|
|
67
|
+
BindKaleidoswapOptions,
|
|
68
|
+
} from './kaleidoswap/contract.js';
|
|
69
|
+
|
|
70
|
+
// ── LSPS1 (Lightning Service Provider channel orders) ───────────────────────
|
|
71
|
+
export {
|
|
72
|
+
LSPS1_TOOLS,
|
|
73
|
+
LSPS1_SPEND_TOOLS,
|
|
74
|
+
isLsps1SpendTool,
|
|
75
|
+
getLsps1Tool,
|
|
76
|
+
bindLsps1Tools,
|
|
77
|
+
} from './lsps1/contract.js';
|
|
78
|
+
export type {
|
|
79
|
+
Lsps1ToolDef,
|
|
80
|
+
Lsps1Handler,
|
|
81
|
+
BindLsps1Options,
|
|
82
|
+
} from './lsps1/contract.js';
|
|
83
|
+
|
|
84
|
+
// ── KaleidoSwap atomic-swap recipe (opt-in — register via Funnel.recipes) ──
|
|
85
|
+
export { kaleidoswapAtomicRecipe } from './recipe/kaleidoswap-atomic.js';
|
|
52
86
|
|
|
53
87
|
// ── Recipes (mobile multi-step: "recipes, not planning") ───────────────────
|
|
54
88
|
export { runRecipe, extractSlots, RecipeRegistry } from './recipe/runner.js';
|
|
@@ -69,6 +103,7 @@ export type { MemoryStoreOptions } from './memory/store.js';
|
|
|
69
103
|
export { createMemoryToolSource } from './memory/tool.js';
|
|
70
104
|
export type {
|
|
71
105
|
AgentProfile,
|
|
106
|
+
MemoryConsolidation,
|
|
72
107
|
MemoryItem,
|
|
73
108
|
MemoryKind,
|
|
74
109
|
MemoryQuery,
|
|
@@ -110,6 +145,14 @@ export { walletHistoryToDocuments, contactsToDocuments } from './knowledge/walle
|
|
|
110
145
|
export type { WalletTx, Contact } from './knowledge/wallet.js';
|
|
111
146
|
export { merchantsToDocuments } from './knowledge/merchants.js';
|
|
112
147
|
export type { Merchant } from './knowledge/merchants.js';
|
|
148
|
+
export { createBtcMapToolSource, BTC_MAP_SAMPLE } from './knowledge/btc-map.js';
|
|
149
|
+
export type {
|
|
150
|
+
BtcMapToolOptions,
|
|
151
|
+
BtcMapMerchant,
|
|
152
|
+
BtcMapFetch,
|
|
153
|
+
LocationProvider,
|
|
154
|
+
LatLng,
|
|
155
|
+
} from './knowledge/btc-map.js';
|
|
113
156
|
|
|
114
157
|
export { Engine } from './engine.js';
|
|
115
158
|
export type { EngineOptions, AgenticOptions, AgenticResult } from './engine.js';
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
KALEIDOSWAP_TOOLS,
|
|
4
|
+
KALEIDOSWAP_SPEND_TOOLS,
|
|
5
|
+
isKaleidoswapSpendTool,
|
|
6
|
+
getKaleidoswapTool,
|
|
7
|
+
kaleidoswapTools,
|
|
8
|
+
bindKaleidoswapTools,
|
|
9
|
+
type KaleidoswapHandler,
|
|
10
|
+
} from './contract.js';
|
|
11
|
+
|
|
12
|
+
describe('KALEIDOSWAP_TOOLS — shape invariants', () => {
|
|
13
|
+
it('exposes the expected tool names', () => {
|
|
14
|
+
const names = KALEIDOSWAP_TOOLS.map((t) => t.name);
|
|
15
|
+
expect(names).toEqual([
|
|
16
|
+
'kaleidoswap_get_assets',
|
|
17
|
+
'kaleidoswap_get_pairs',
|
|
18
|
+
'kaleidoswap_get_quote',
|
|
19
|
+
'kaleidoswap_get_nodeinfo',
|
|
20
|
+
'kaleidoswap_place_order',
|
|
21
|
+
'kaleidoswap_get_order_status',
|
|
22
|
+
'kaleidoswap_get_order_history',
|
|
23
|
+
'kaleidoswap_atomic_init',
|
|
24
|
+
'kaleidoswap_atomic_execute',
|
|
25
|
+
'kaleidoswap_atomic_status',
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('every tool has a group and a parameters object', () => {
|
|
30
|
+
for (const t of KALEIDOSWAP_TOOLS) {
|
|
31
|
+
expect(['market', 'orders', 'atomic']).toContain(t.group);
|
|
32
|
+
expect(t.parameters).toBeDefined();
|
|
33
|
+
expect((t.parameters as any).type).toBe('object');
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('marks every spend tool as requiresConfirmation', () => {
|
|
38
|
+
for (const t of KALEIDOSWAP_TOOLS) {
|
|
39
|
+
expect(!!t.spend).toBe(!!t.requiresConfirmation);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('lists every spend tool exactly once in KALEIDOSWAP_SPEND_TOOLS', () => {
|
|
44
|
+
const expected = KALEIDOSWAP_TOOLS.filter((t) => t.spend).map((t) => t.name).sort();
|
|
45
|
+
expect([...KALEIDOSWAP_SPEND_TOOLS].sort()).toEqual(expected);
|
|
46
|
+
// Sanity: place_order, atomic_init, atomic_execute are spend; the rest aren't.
|
|
47
|
+
expect(expected).toEqual([
|
|
48
|
+
'kaleidoswap_atomic_execute',
|
|
49
|
+
'kaleidoswap_atomic_init',
|
|
50
|
+
'kaleidoswap_place_order',
|
|
51
|
+
]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('isKaleidoswapSpendTool agrees with the set', () => {
|
|
55
|
+
expect(isKaleidoswapSpendTool('kaleidoswap_place_order')).toBe(true);
|
|
56
|
+
expect(isKaleidoswapSpendTool('kaleidoswap_get_pairs')).toBe(false);
|
|
57
|
+
expect(isKaleidoswapSpendTool('not_a_tool')).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('getKaleidoswapTool returns by name', () => {
|
|
61
|
+
expect(getKaleidoswapTool('kaleidoswap_get_quote')?.group).toBe('market');
|
|
62
|
+
expect(getKaleidoswapTool('nope')).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('kaleidoswapTools(groups)', () => {
|
|
67
|
+
it('returns all tools when no group filter', () => {
|
|
68
|
+
expect(kaleidoswapTools().length).toBe(KALEIDOSWAP_TOOLS.length);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('filters by group', () => {
|
|
72
|
+
const market = kaleidoswapTools({ groups: ['market'] });
|
|
73
|
+
expect(market.every((t) => t.group === 'market')).toBe(true);
|
|
74
|
+
expect(market.map((t) => t.name)).toContain('kaleidoswap_get_quote');
|
|
75
|
+
expect(market.map((t) => t.name)).not.toContain('kaleidoswap_place_order');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('combines multiple groups', () => {
|
|
79
|
+
const readPlusOrders = kaleidoswapTools({ groups: ['market', 'orders'] });
|
|
80
|
+
expect(readPlusOrders.some((t) => t.name === 'kaleidoswap_atomic_init')).toBe(false);
|
|
81
|
+
expect(readPlusOrders.some((t) => t.name === 'kaleidoswap_place_order')).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('bindKaleidoswapTools', () => {
|
|
86
|
+
// Handlers that just echo their args so we can verify wiring.
|
|
87
|
+
const echoHandlers = (): Record<string, KaleidoswapHandler> => ({
|
|
88
|
+
kaleidoswap_get_assets: async () => ({ ok: true, tool: 'get_assets' }),
|
|
89
|
+
kaleidoswap_get_pairs: async () => ({ ok: true, tool: 'get_pairs' }),
|
|
90
|
+
kaleidoswap_get_quote: async (a) => ({ ok: true, tool: 'get_quote', args: a }),
|
|
91
|
+
kaleidoswap_get_nodeinfo: async () => ({ ok: true, tool: 'get_nodeinfo' }),
|
|
92
|
+
kaleidoswap_place_order: async (a) => ({ ok: true, tool: 'place_order', args: a }),
|
|
93
|
+
kaleidoswap_get_order_status: async (a) => ({ ok: true, tool: 'get_order_status', args: a }),
|
|
94
|
+
kaleidoswap_get_order_history: async (a) => ({ ok: true, tool: 'get_order_history', args: a }),
|
|
95
|
+
kaleidoswap_atomic_init: async (a) => ({ ok: true, tool: 'atomic_init', args: a }),
|
|
96
|
+
kaleidoswap_atomic_execute: async (a) => ({ ok: true, tool: 'atomic_execute', args: a }),
|
|
97
|
+
kaleidoswap_atomic_status: async (a) => ({ ok: true, tool: 'atomic_status', args: a }),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('binds every tool when all handlers are present', () => {
|
|
101
|
+
const src = bindKaleidoswapTools(echoHandlers());
|
|
102
|
+
const tools = src.listTools();
|
|
103
|
+
expect(tools.length).toBe(KALEIDOSWAP_TOOLS.length);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('preserves descriptions and the spend gate (requiresConfirmation)', () => {
|
|
107
|
+
const src = bindKaleidoswapTools(echoHandlers());
|
|
108
|
+
const place = src.listTools().find((t) => t.name === 'kaleidoswap_place_order');
|
|
109
|
+
const pairs = src.listTools().find((t) => t.name === 'kaleidoswap_get_pairs');
|
|
110
|
+
expect(place?.requiresConfirmation).toBe(true);
|
|
111
|
+
expect(pairs?.requiresConfirmation).toBeFalsy();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('dispatches execute() to the right handler with args', async () => {
|
|
115
|
+
const src = bindKaleidoswapTools(echoHandlers());
|
|
116
|
+
const r = await src.execute('kaleidoswap_get_quote', { from_asset: 'BTC', to_asset: 'USDT', amount: 100_000 });
|
|
117
|
+
expect(r).toMatchObject({ ok: true, tool: 'get_quote', args: { from_asset: 'BTC', amount: 100_000 } });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('throws when a handler is missing and allowMissing is false', () => {
|
|
121
|
+
const handlers = { kaleidoswap_get_pairs: echoHandlers().kaleidoswap_get_pairs };
|
|
122
|
+
expect(() => bindKaleidoswapTools(handlers)).toThrow(/no handler/);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('skips missing handlers when allowMissing is true', () => {
|
|
126
|
+
const handlers: Record<string, KaleidoswapHandler> = {
|
|
127
|
+
kaleidoswap_get_pairs: async () => ({ ok: true }),
|
|
128
|
+
kaleidoswap_get_quote: async () => ({ ok: true }),
|
|
129
|
+
};
|
|
130
|
+
const src = bindKaleidoswapTools(handlers, { allowMissing: true });
|
|
131
|
+
const names = src.listTools().map((t) => t.name);
|
|
132
|
+
expect(names).toEqual(['kaleidoswap_get_pairs', 'kaleidoswap_get_quote']);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('respects the groups filter when binding', () => {
|
|
136
|
+
const src = bindKaleidoswapTools(echoHandlers(), { groups: ['market'] });
|
|
137
|
+
const names = src.listTools().map((t) => t.name);
|
|
138
|
+
expect(names).toContain('kaleidoswap_get_quote');
|
|
139
|
+
expect(names).not.toContain('kaleidoswap_place_order');
|
|
140
|
+
expect(names).not.toContain('kaleidoswap_atomic_init');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('uses opts.id for the ToolSource id', () => {
|
|
144
|
+
const src = bindKaleidoswapTools(echoHandlers(), { id: 'maker-prod' });
|
|
145
|
+
expect(src.id).toBe('maker-prod');
|
|
146
|
+
});
|
|
147
|
+
});
|