@kaleidorg/mind 0.4.0 → 0.5.1
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/funnel.d.ts +19 -0
- package/dist/funnel.d.ts.map +1 -1
- package/dist/funnel.js +48 -10
- package/dist/funnel.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/kaleidoswap/contract.d.ts +3 -3
- package/dist/kaleidoswap/contract.d.ts.map +1 -1
- package/dist/kaleidoswap/contract.js +16 -4
- package/dist/kaleidoswap/contract.js.map +1 -1
- package/dist/knowledge/bitcoin-copilot.d.ts.map +1 -1
- package/dist/knowledge/bitcoin-copilot.js +102 -0
- package/dist/knowledge/bitcoin-copilot.js.map +1 -1
- package/dist/knowledge/btc-map.d.ts +14 -17
- package/dist/knowledge/btc-map.d.ts.map +1 -1
- package/dist/knowledge/btc-map.js +66 -266
- package/dist/knowledge/btc-map.js.map +1 -1
- package/dist/lsps1/contract.d.ts.map +1 -1
- package/dist/lsps1/contract.js +28 -10
- package/dist/lsps1/contract.js.map +1 -1
- package/dist/qvac/parse.d.ts +15 -0
- package/dist/qvac/parse.d.ts.map +1 -1
- package/dist/qvac/parse.js +68 -5
- package/dist/qvac/parse.js.map +1 -1
- package/dist/qvac/text.d.ts.map +1 -1
- package/dist/qvac/text.js +4 -0
- package/dist/qvac/text.js.map +1 -1
- package/dist/recipe/buy-asset-channel.d.ts +26 -0
- package/dist/recipe/buy-asset-channel.d.ts.map +1 -0
- package/dist/recipe/buy-asset-channel.js +112 -0
- package/dist/recipe/buy-asset-channel.js.map +1 -0
- package/dist/recipe/kaleidoswap-atomic.d.ts +26 -18
- package/dist/recipe/kaleidoswap-atomic.d.ts.map +1 -1
- package/dist/recipe/kaleidoswap-atomic.js +101 -63
- package/dist/recipe/kaleidoswap-atomic.js.map +1 -1
- package/dist/recipe/kaleidoswap-channel-order.d.ts +35 -0
- package/dist/recipe/kaleidoswap-channel-order.d.ts.map +1 -0
- package/dist/recipe/kaleidoswap-channel-order.js +493 -0
- package/dist/recipe/kaleidoswap-channel-order.js.map +1 -0
- package/dist/recipe/kaleidoswap-price.d.ts +21 -0
- package/dist/recipe/kaleidoswap-price.d.ts.map +1 -0
- package/dist/recipe/kaleidoswap-price.js +57 -0
- package/dist/recipe/kaleidoswap-price.js.map +1 -0
- package/dist/recipe/runner.d.ts +7 -1
- package/dist/recipe/runner.d.ts.map +1 -1
- package/dist/recipe/runner.js +115 -29
- package/dist/recipe/runner.js.map +1 -1
- package/dist/recipe/swap.d.ts +26 -1
- package/dist/recipe/swap.d.ts.map +1 -1
- package/dist/recipe/swap.js +108 -13
- package/dist/recipe/swap.js.map +1 -1
- package/dist/recipe/types.d.ts +25 -1
- package/dist/recipe/types.d.ts.map +1 -1
- package/dist/skills/registry.d.ts +33 -1
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js +45 -1
- package/dist/skills/registry.js.map +1 -1
- package/package.json +1 -1
- package/skills/README.md +3 -0
- package/skills/kaleido-lsps/SKILL.md +101 -43
- package/skills/kaleido-trading/SKILL.md +81 -31
- package/skills/merchant-finder/SKILL.md +96 -66
- package/skills/rgb-lightning-node/SKILL.md +108 -0
- package/skills/wallet-assistant/SKILL.md +32 -21
- package/src/funnel.ts +66 -11
- package/src/index.ts +14 -2
- package/src/kaleidoswap/contract.test.ts +7 -2
- package/src/kaleidoswap/contract.ts +27 -5
- package/src/knowledge/bitcoin-copilot.ts +111 -0
- package/src/knowledge/btc-map.test.ts +53 -96
- package/src/knowledge/btc-map.ts +72 -287
- package/src/lsps1/contract.ts +32 -14
- package/src/qvac/parse.test.ts +70 -1
- package/src/qvac/parse.ts +71 -5
- package/src/qvac/text.ts +4 -0
- package/src/recipe/buy-asset-channel.test.ts +148 -0
- package/src/recipe/buy-asset-channel.ts +118 -0
- package/src/recipe/kaleidoswap-atomic.test.ts +134 -61
- package/src/recipe/kaleidoswap-atomic.ts +112 -66
- package/src/recipe/kaleidoswap-channel-order.test.ts +333 -0
- package/src/recipe/kaleidoswap-channel-order.ts +548 -0
- package/src/recipe/kaleidoswap-price.ts +68 -0
- package/src/recipe/recipe.test.ts +61 -5
- package/src/recipe/runner.ts +128 -31
- package/src/recipe/swap.ts +109 -13
- package/src/recipe/types.ts +25 -1
- package/src/skills/registry.ts +52 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rgb-lightning-node
|
|
3
|
+
description: "Drive the user's local RGB Lightning Node (RLN) — read its pubkey/status, whitelist a swap, or create Lightning/RGB receive invoices. Triggers when the user asks about the node, needs an invoice, or is mid-atomic-swap and the maker needs the node pubkey or a swapstring whitelisted."
|
|
4
|
+
tools: rln_get_node_info, rln_whitelist_swap, rln_create_ln_invoice, rln_create_rgb_invoice
|
|
5
|
+
triggers: node, nodeinfo, pubkey, peer, channels, whitelist, taker, swapstring, invoice, receive, rgb invoice, ln invoice
|
|
6
|
+
metadata:
|
|
7
|
+
author: kaleidoswap
|
|
8
|
+
version: "0.1.0"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# RGB Lightning Node (taker-side)
|
|
12
|
+
|
|
13
|
+
You drive the **user's own** RGB Lightning Node running locally. In a KaleidoSwap
|
|
14
|
+
atomic swap the **maker** owns init / execute / status (those are
|
|
15
|
+
`kaleidoswap_atomic_*` tools, separate REST endpoints). The node's job in a
|
|
16
|
+
swap is narrow: **expose its pubkey and whitelist the maker's swapstring**.
|
|
17
|
+
The node does NOT init or execute swaps.
|
|
18
|
+
|
|
19
|
+
## Critical rules
|
|
20
|
+
|
|
21
|
+
You have **no knowledge** of the node's pubkey, channel state, balance, or any
|
|
22
|
+
invoice contents. Every value in your reply MUST come from a tool result
|
|
23
|
+
returned in the CURRENT turn — never invent a pubkey, channel id, invoice
|
|
24
|
+
string, or sats balance. Never reuse a value from a previous turn.
|
|
25
|
+
|
|
26
|
+
**Calling the tool IS the answer.** If the user asks "what's my pubkey?", call
|
|
27
|
+
`rln_get_node_info` — do not describe how to fetch it.
|
|
28
|
+
|
|
29
|
+
## When to use each tool
|
|
30
|
+
|
|
31
|
+
### `rln_get_node_info` — no args
|
|
32
|
+
Returns:
|
|
33
|
+
- `pubkey` — the node's identity (32-byte hex).
|
|
34
|
+
- `num_channels` — total channels (may include unusable ones).
|
|
35
|
+
- `num_usable_channels` — subset that can route a payment right now.
|
|
36
|
+
- `local_balance_sat` — **sats YOU own** across all channels. This is your
|
|
37
|
+
**spend** capacity (outbound). It is **NOT** receive capacity, **NOT**
|
|
38
|
+
inbound liquidity, and **NOT** total channel capacity.
|
|
39
|
+
- `pending_outbound_payments_sat` — in-flight, temporarily locked.
|
|
40
|
+
- `num_peers` — currently connected peers.
|
|
41
|
+
|
|
42
|
+
Call this when:
|
|
43
|
+
- The user asks about the node, pubkey, peers, channel count, or how much
|
|
44
|
+
they can **spend**.
|
|
45
|
+
- An atomic swap is in progress and the maker needs `taker_pubkey` —
|
|
46
|
+
fetch the pubkey from this tool's `pubkey` field and pass it to
|
|
47
|
+
`kaleidoswap_atomic_execute`.
|
|
48
|
+
|
|
49
|
+
**Do NOT** use this tool's `local_balance_sat` to answer a question about
|
|
50
|
+
**inbound liquidity / receive capacity** — that is a different quantity
|
|
51
|
+
(the peer's side of each channel, not yours). For "how much can I receive?",
|
|
52
|
+
the LSPS skill answers what's available to BUY (`lsp_get_info`); the current
|
|
53
|
+
remote-balance breakdown isn't exposed by this skill's tools.
|
|
54
|
+
|
|
55
|
+
### `rln_whitelist_swap` — { swapstring } — 🔒 confirm-gated
|
|
56
|
+
Tell the node "I accept this swap." Args: the `swapstring` returned by
|
|
57
|
+
`kaleidoswap_atomic_init`. The node validates and stores it; **no funds move
|
|
58
|
+
here**, but the user is committing to the swap so the engine pauses for
|
|
59
|
+
confirmation.
|
|
60
|
+
|
|
61
|
+
Call this **after** `kaleidoswap_atomic_init` and **before**
|
|
62
|
+
`kaleidoswap_atomic_execute`. Never call with an empty or invented swapstring
|
|
63
|
+
— the node will reject it.
|
|
64
|
+
|
|
65
|
+
### `rln_create_ln_invoice` — Lightning invoice for receiving sats
|
|
66
|
+
Args:
|
|
67
|
+
- `amount_sats` (optional) — omit for an amountless invoice.
|
|
68
|
+
- `expiry_sec` (default 3600) — invoice TTL in seconds.
|
|
69
|
+
- `asset_id` + `asset_amount` — optional, for RGB-over-Lightning.
|
|
70
|
+
|
|
71
|
+
Use when the user wants to **receive** a Lightning payment. Do NOT call inside
|
|
72
|
+
an atomic swap flow unless the user explicitly asked to invoice someone.
|
|
73
|
+
|
|
74
|
+
### `rln_create_rgb_invoice` — on-chain RGB receive invoice
|
|
75
|
+
Args:
|
|
76
|
+
- `min_confirmations` (default 1).
|
|
77
|
+
- `witness` (default false).
|
|
78
|
+
- `asset_id` (optional — omit for an any-asset invoice).
|
|
79
|
+
- `expiration_timestamp` (optional, Unix seconds).
|
|
80
|
+
|
|
81
|
+
Use when the user wants to **receive** an RGB asset directly (not over
|
|
82
|
+
Lightning). Outside the atomic swap flow.
|
|
83
|
+
|
|
84
|
+
## The maker / node split
|
|
85
|
+
|
|
86
|
+
A user-driven swap on KaleidoSwap is a two-service flow. Keep them straight:
|
|
87
|
+
|
|
88
|
+
| Step | Owner | Tool |
|
|
89
|
+
|------|-------|------|
|
|
90
|
+
| Quote | maker | `kaleidoswap_get_quote` |
|
|
91
|
+
| Init | maker | `kaleidoswap_atomic_init` (returns swapstring + payment_hash) |
|
|
92
|
+
| Pubkey | **node** | `rln_get_node_info` (read `pubkey`) |
|
|
93
|
+
| Whitelist | **node** | `rln_whitelist_swap` (pass the swapstring) |
|
|
94
|
+
| Execute | maker | `kaleidoswap_atomic_execute` (needs swapstring + taker_pubkey + payment_hash) |
|
|
95
|
+
| Status | maker | `kaleidoswap_atomic_status` |
|
|
96
|
+
|
|
97
|
+
The node's two contributions to the swap are the **pubkey** and the
|
|
98
|
+
**whitelist ack** — nothing more. Don't reach for `/makerinit` or
|
|
99
|
+
`/makerexecute`; those are for nodes that act AS the maker, which is not us.
|
|
100
|
+
|
|
101
|
+
## Reply style
|
|
102
|
+
|
|
103
|
+
- One short sentence built from the tool result.
|
|
104
|
+
- Pubkeys are long hex strings — quote them in monospace if you can, never
|
|
105
|
+
truncate them when the user explicitly asked for them.
|
|
106
|
+
- For `rln_get_node_info`, if the user just said "what's my node status?",
|
|
107
|
+
surface pubkey + num_usable_channels + local_balance_sat. Don't dump the
|
|
108
|
+
whole `details` object.
|
|
@@ -1,38 +1,49 @@
|
|
|
1
1
|
---
|
|
2
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,
|
|
4
|
-
tools: get_balances,
|
|
5
|
-
triggers: balance, pay, send, receive, address, invoice, transactions, contact, funds, money,
|
|
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, or quote a swap (e.g. "how many sats is 10 USDT?"). Triggers when the user asks about their balance, wants to receive or send money, pay an invoice, pay a contact, or convert between BTC and supported assets.
|
|
4
|
+
tools: get_balances, resolve_contact, send_payment, rln_pay_invoice, rln_create_ln_invoice, spark_create_invoice, kaleidoswap_get_quote
|
|
5
|
+
triggers: balance, pay, send, receive, address, invoice, transactions, contact, funds, money, sats
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Wallet assistant
|
|
9
9
|
|
|
10
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
|
|
11
|
+
get real data — NEVER invent a balance, address, amount, price, or quote.
|
|
12
12
|
|
|
13
13
|
## Critical rules
|
|
14
14
|
|
|
15
|
-
You have no knowledge of balances,
|
|
16
|
-
in your reply MUST come from a tool result returned in the CURRENT
|
|
17
|
-
not reuse a number from a previous turn.
|
|
15
|
+
You have no knowledge of balances, addresses, invoices, prices, or quotes.
|
|
16
|
+
Every value in your reply MUST come from a tool result returned in the CURRENT
|
|
17
|
+
turn — do not reuse a number from a previous turn.
|
|
18
|
+
|
|
19
|
+
NEVER mention the exact name of any tool (such as "kaleidoswap_get_quote") in
|
|
20
|
+
your text reply to the user. Only use tools via the proper function call
|
|
21
|
+
format when needed; describe what you are doing in plain language.
|
|
18
22
|
|
|
19
23
|
When a tool returns multiple fields, **report all the load-bearing ones**:
|
|
20
|
-
-
|
|
24
|
+
- The balance tool may return `{confirmed, pending, total}` — when `pending`
|
|
21
25
|
is non-zero, report BOTH. `confirmed` is spendable; `pending` is settling
|
|
22
26
|
and is NOT spendable yet. The user needs to know the difference.
|
|
23
|
-
-
|
|
24
|
-
|
|
27
|
+
- The quote tool returns display strings (e.g. the to amount and fee in
|
|
28
|
+
human readable form). Read these strings verbatim — do NOT do unit math
|
|
29
|
+
yourself.
|
|
25
30
|
|
|
26
31
|
## Rules
|
|
27
32
|
|
|
28
|
-
- Balance / "how much do I have" →
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
- **Balance / "how much do I have"** → use the balance tool, then state the
|
|
34
|
+
number.
|
|
35
|
+
- **Receive / "an invoice for N sats"** → use the invoice creation tool (for
|
|
36
|
+
the appropriate layer) with the amount.
|
|
37
|
+
- **"How many sats is N USDT?" / "What's 0.1 BTC in USDT?" / "convert N X
|
|
38
|
+
to Y"** → use the quote/conversion tool and
|
|
39
|
+
read the display fields from the result. Supported pairs: any of
|
|
40
|
+
BTC/USDT/XAUT against each other. Fiat (EUR/USD/GBP) is NOT quoted by
|
|
41
|
+
the maker — if the user asks "how much sats is 10 EUR", say so plainly
|
|
42
|
+
and offer USDT as the closest analogue (USDT is a USD-pegged stablecoin).
|
|
43
|
+
- **Pay a Lightning invoice** → use the lightning payment tool.
|
|
44
|
+
- **Send to a person/amount** → first resolve the contact, then use the send
|
|
45
|
+
payment tool with the amount in sats and the recipient. State the amount and
|
|
46
|
+
destination; the app asks the user to confirm before it sends.
|
|
47
|
+
|
|
48
|
+
Keep replies short, but never drop a balance component, a fee, or the
|
|
49
|
+
quote's `rfq_id` when present.
|
package/src/funnel.ts
CHANGED
|
@@ -31,6 +31,7 @@ import type { Recipe } from './recipe/types.js';
|
|
|
31
31
|
import { SkillRegistry } from './skills/registry.js';
|
|
32
32
|
import type { Skill } from './skills/types.js';
|
|
33
33
|
import type { LLMProvider } from './providers/types.js';
|
|
34
|
+
import type { Retriever } from './rag/retriever.js';
|
|
34
35
|
import type { ConfirmDecision, Message, ToolResult } from './types.js';
|
|
35
36
|
|
|
36
37
|
/** Base system prompt for the wallet assistant. Hosts may override. */
|
|
@@ -53,6 +54,16 @@ export const DEFAULT_WALLET_SYSTEM = [
|
|
|
53
54
|
' the required field missing.',
|
|
54
55
|
'5. All BTC amounts are in satoshis. Asset codes are case-insensitive but the',
|
|
55
56
|
' canonical forms are BTC, USDT, XAUT — do not silently shorten to USD, XAU.',
|
|
57
|
+
'6. NEVER name a tool, function, endpoint, or argument key in your reply.',
|
|
58
|
+
' Tools are private plumbing. Bad: "use kaleidoswap_get_quote with amount 1".',
|
|
59
|
+
' Good: "I can quote that for you — one moment." If you need information',
|
|
60
|
+
' from the user (like an amount), ASK in plain English without referencing',
|
|
61
|
+
' how the system will use it.',
|
|
62
|
+
'7. A price/rate question (e.g. "price of USDT", "BTC price", "how much is',
|
|
63
|
+
' 1 USDT") is a UNIT QUOTE — answer with the value of 1 of the named asset',
|
|
64
|
+
' in the denomination the user asked for (default: sats when pricing USDT/',
|
|
65
|
+
' XAUT, USDT when pricing BTC). Never ask the user "how much do you want"',
|
|
66
|
+
' for a price question.',
|
|
56
67
|
'',
|
|
57
68
|
'Keep replies short and friendly. When a tool returns multiple fields, surface',
|
|
58
69
|
"the ones that matter — never collapse a structured result to a single number",
|
|
@@ -98,12 +109,14 @@ export interface FunnelCallbacks {
|
|
|
98
109
|
arguments: Record<string, unknown>;
|
|
99
110
|
result: unknown;
|
|
100
111
|
}) => void;
|
|
101
|
-
onConfirm?: (call: { name: string; arguments: Record<string, unknown
|
|
112
|
+
onConfirm?: (call: { name: string; arguments: Record<string, unknown>; summary?: string }) => Promise<ConfirmDecision>;
|
|
102
113
|
}
|
|
103
114
|
|
|
104
115
|
export interface FunnelResult {
|
|
105
116
|
text: string;
|
|
106
117
|
tier: 'fast' | 'recipe' | 'agentic';
|
|
118
|
+
/** What handled the turn: the intent (fast), recipe name (recipe), or skill name (agentic). */
|
|
119
|
+
route?: string;
|
|
107
120
|
/** Fast tier only: the matched intent + raw tool result (e.g. for a balance card). */
|
|
108
121
|
intent?: string;
|
|
109
122
|
data?: unknown;
|
|
@@ -132,6 +145,19 @@ export interface FunnelOptions {
|
|
|
132
145
|
renderFast?: (intent: string, result: unknown) => string;
|
|
133
146
|
/** Diagnostics sink (tier routing, tool calls). Default: silent. */
|
|
134
147
|
log?: (message: string) => void;
|
|
148
|
+
/**
|
|
149
|
+
* Optional retriever for AUTO-injecting relevant knowledge chunks into the
|
|
150
|
+
* agentic-tier system prompt (T1 only — recipes/fast-path are deterministic
|
|
151
|
+
* and don't need it). When set, the top-`topKRag` chunks for the user's
|
|
152
|
+
* query are prepended as `## Relevant context`. Default: no auto-inject
|
|
153
|
+
* (the `search_knowledge` tool stays available for on-demand lookups).
|
|
154
|
+
*
|
|
155
|
+
* Small models often don't choose to call search_knowledge; auto-inject
|
|
156
|
+
* makes the corpus useful by default without the model having to opt in.
|
|
157
|
+
*/
|
|
158
|
+
retriever?: Retriever;
|
|
159
|
+
/** How many chunks to auto-inject when `retriever` is set. Default 3. */
|
|
160
|
+
topKRag?: number;
|
|
135
161
|
}
|
|
136
162
|
|
|
137
163
|
function defaultRenderFast(intent: string, r: any): string {
|
|
@@ -157,6 +183,8 @@ export class Funnel {
|
|
|
157
183
|
private readonly getSettings: () => FunnelSettings;
|
|
158
184
|
private readonly renderFast: (intent: string, result: unknown) => string;
|
|
159
185
|
private readonly log: (message: string) => void;
|
|
186
|
+
private readonly retriever?: Retriever;
|
|
187
|
+
private readonly topKRag: number;
|
|
160
188
|
|
|
161
189
|
/** Skill registry, rebuilt only when the disabled-skills set changes. */
|
|
162
190
|
private skillsCache: { key: string; reg: SkillRegistry } | null = null;
|
|
@@ -176,6 +204,8 @@ export class Funnel {
|
|
|
176
204
|
this.getSettings = opts.getSettings ?? (() => ({}));
|
|
177
205
|
this.renderFast = opts.renderFast ?? defaultRenderFast;
|
|
178
206
|
this.log = opts.log ?? (() => {});
|
|
207
|
+
if (opts.retriever) this.retriever = opts.retriever;
|
|
208
|
+
this.topKRag = opts.topKRag ?? 3;
|
|
179
209
|
}
|
|
180
210
|
|
|
181
211
|
/** Skills currently enabled (e.g. for a skills sheet). */
|
|
@@ -205,18 +235,24 @@ export class Funnel {
|
|
|
205
235
|
if (fast && (await this.registry.getDef(fast.tool))) {
|
|
206
236
|
this.log(`tier=fast-path → ${fast.tool}`);
|
|
207
237
|
const r = await this.registry.execute(fast.tool, fast.args);
|
|
208
|
-
return { text: this.renderFast(fast.intent.name, r), tier: 'fast', intent: fast.intent.name, data: r };
|
|
238
|
+
return { text: this.renderFast(fast.intent.name, r), tier: 'fast', route: fast.intent.name, intent: fast.intent.name, data: r };
|
|
209
239
|
}
|
|
210
240
|
|
|
211
|
-
// ── T2: recipe multi-step — fires
|
|
212
|
-
//
|
|
213
|
-
//
|
|
241
|
+
// ── T2: recipe multi-step — fires when:
|
|
242
|
+
// (a) the recipe is confident given its deterministic slots, OR
|
|
243
|
+
// (b) `forceModelExtract` is on — the LLM does the actual extraction
|
|
244
|
+
// inside runRecipe, so we don't gate on the regex result. If the
|
|
245
|
+
// LLM still doesn't yield enough, runRecipe returns status:
|
|
246
|
+
// 'needs-info' with a friendly "please specify X" instead of
|
|
247
|
+
// running steps with bad data.
|
|
248
|
+
// Either way the registry must implement the recipe's final action.
|
|
214
249
|
const recipe = this.recipes.select(text);
|
|
215
250
|
const slots = recipe?.extract?.(text) ?? null;
|
|
251
|
+
const deterministicallyConfident =
|
|
252
|
+
!!slots && (recipe?.confident ? recipe.confident(slots) : Object.keys(slots).length > 0);
|
|
216
253
|
const fires =
|
|
217
254
|
!!recipe &&
|
|
218
|
-
|
|
219
|
-
(recipe.confident ? recipe.confident(slots) : Object.keys(slots).length > 0) &&
|
|
255
|
+
(recipe.forceModelExtract === true || deterministicallyConfident) &&
|
|
220
256
|
!!(await this.registry.getDef(recipe.final.tool));
|
|
221
257
|
if (recipe && fires) {
|
|
222
258
|
this.log(`tier=recipe:${recipe.name} slots=${JSON.stringify(slots)}`);
|
|
@@ -229,19 +265,38 @@ export class Funnel {
|
|
|
229
265
|
cbs.onStep?.(name);
|
|
230
266
|
},
|
|
231
267
|
});
|
|
232
|
-
return { text: res.text, tier: 'recipe' };
|
|
268
|
+
return { text: res.text, tier: 'recipe', route: recipe.name };
|
|
233
269
|
}
|
|
234
270
|
|
|
235
271
|
// ── T1: skill-scoped agentic loop ──
|
|
236
272
|
const skills = this.skillsFor(settings.disabledSkills);
|
|
237
273
|
const skill = skills.select(text);
|
|
238
|
-
|
|
274
|
+
let base = settings.persona ? `${this.system}\n\n## Your persona\n${settings.persona}` : this.system;
|
|
275
|
+
|
|
276
|
+
// Auto-inject relevant knowledge chunks (best-effort — corpus is grounding
|
|
277
|
+
// truth, history is conversational context; both reach the model but the
|
|
278
|
+
// RAG block sits above history so the model treats it as authoritative).
|
|
279
|
+
// Only fires for agentic turns and only when the host opts in via
|
|
280
|
+
// `retriever` AND the user hasn't disabled RAG in settings.
|
|
281
|
+
const ragOn = settings.ragEnabled !== false;
|
|
282
|
+
if (this.retriever && ragOn && this.topKRag > 0) {
|
|
283
|
+
try {
|
|
284
|
+
const hits = await this.retriever.search(text, this.topKRag);
|
|
285
|
+
if (hits.length) {
|
|
286
|
+
const chunks = hits.map((h) => `- ${h.text}`).join('\n');
|
|
287
|
+
base = `${base}\n\n## Relevant context (read first; trust this over conversation history)\n${chunks}`;
|
|
288
|
+
this.log(`rag injected ${hits.length} chunks`);
|
|
289
|
+
}
|
|
290
|
+
} catch (e) {
|
|
291
|
+
this.log(`rag failed: ${(e as Error)?.message ?? e}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
239
295
|
const { system, allowedTools } = skills.compose(base, skill);
|
|
240
296
|
|
|
241
297
|
// Ambient tools stay available even when a skill narrows the set — gated
|
|
242
298
|
// by the user's memory/knowledge toggles (default on).
|
|
243
299
|
const memoryOn = settings.memoryEnabled !== false;
|
|
244
|
-
const ragOn = settings.ragEnabled !== false;
|
|
245
300
|
const ambient = [...(memoryOn ? AMBIENT_MEMORY : []), ...(ragOn ? AMBIENT_RAG : [])];
|
|
246
301
|
const disabledAmbient = [...(memoryOn ? [] : AMBIENT_MEMORY), ...(ragOn ? [] : AMBIENT_RAG)];
|
|
247
302
|
let scoped: string[] | undefined;
|
|
@@ -280,6 +335,6 @@ export class Funnel {
|
|
|
280
335
|
onToolResult: cbs.onToolResult,
|
|
281
336
|
onConfirm: cbs.onConfirm,
|
|
282
337
|
});
|
|
283
|
-
return { text: res.text ?? '', tier: 'agentic', toolCalls: res.toolCalls, turns: res.turns };
|
|
338
|
+
return { text: res.text ?? '', tier: 'agentic', route: skill?.name, toolCalls: res.toolCalls, turns: res.turns };
|
|
284
339
|
}
|
|
285
340
|
}
|
package/src/index.ts
CHANGED
|
@@ -81,8 +81,19 @@ export type {
|
|
|
81
81
|
BindLsps1Options,
|
|
82
82
|
} from './lsps1/contract.js';
|
|
83
83
|
|
|
84
|
-
// ── KaleidoSwap
|
|
84
|
+
// ── KaleidoSwap recipes (opt-in — register via Funnel.recipes) ──
|
|
85
|
+
// price recipe is read-only (quote-only); atomic recipe runs the full swap.
|
|
86
|
+
// Register the price recipe FIRST so phrasings like "BTC price" are answered
|
|
87
|
+
// without firing any spend.
|
|
88
|
+
export { kaleidoswapPriceRecipe } from './recipe/kaleidoswap-price.js';
|
|
85
89
|
export { kaleidoswapAtomicRecipe } from './recipe/kaleidoswap-atomic.js';
|
|
90
|
+
export {
|
|
91
|
+
kaleidoswapChannelOrderRecipe,
|
|
92
|
+
extractChannelOrder,
|
|
93
|
+
} from './recipe/kaleidoswap-channel-order.js';
|
|
94
|
+
|
|
95
|
+
// ── Buy-an-asset-channel recipe (opt-in — register via Funnel.recipes) ─────
|
|
96
|
+
export { buyAssetChannelRecipe, extractBuyAsset } from './recipe/buy-asset-channel.js';
|
|
86
97
|
|
|
87
98
|
// ── Recipes (mobile multi-step: "recipes, not planning") ───────────────────
|
|
88
99
|
export { runRecipe, extractSlots, RecipeRegistry } from './recipe/runner.js';
|
|
@@ -145,7 +156,7 @@ export { walletHistoryToDocuments, contactsToDocuments } from './knowledge/walle
|
|
|
145
156
|
export type { WalletTx, Contact } from './knowledge/wallet.js';
|
|
146
157
|
export { merchantsToDocuments } from './knowledge/merchants.js';
|
|
147
158
|
export type { Merchant } from './knowledge/merchants.js';
|
|
148
|
-
export { createBtcMapToolSource
|
|
159
|
+
export { createBtcMapToolSource } from './knowledge/btc-map.js';
|
|
149
160
|
export type {
|
|
150
161
|
BtcMapToolOptions,
|
|
151
162
|
BtcMapMerchant,
|
|
@@ -165,6 +176,7 @@ export {
|
|
|
165
176
|
SkillRegistry,
|
|
166
177
|
parseSkill,
|
|
167
178
|
keywordSelector,
|
|
179
|
+
createEmbeddingSkillSelector,
|
|
168
180
|
READ_REFERENCE_TOOL,
|
|
169
181
|
} from './skills/registry.js';
|
|
170
182
|
export { createSkillReferenceToolSource } from './skills/reference-source.js';
|
|
@@ -23,12 +23,14 @@ describe('KALEIDOSWAP_TOOLS — shape invariants', () => {
|
|
|
23
23
|
'kaleidoswap_atomic_init',
|
|
24
24
|
'kaleidoswap_atomic_execute',
|
|
25
25
|
'kaleidoswap_atomic_status',
|
|
26
|
+
'kaleidoswap_lsp_quote_asset_channel',
|
|
27
|
+
'kaleidoswap_lsp_create_asset_channel',
|
|
26
28
|
]);
|
|
27
29
|
});
|
|
28
30
|
|
|
29
31
|
it('every tool has a group and a parameters object', () => {
|
|
30
32
|
for (const t of KALEIDOSWAP_TOOLS) {
|
|
31
|
-
expect(['market', 'orders', 'atomic']).toContain(t.group);
|
|
33
|
+
expect(['market', 'orders', 'atomic', 'liquidity']).toContain(t.group);
|
|
32
34
|
expect(t.parameters).toBeDefined();
|
|
33
35
|
expect((t.parameters as any).type).toBe('object');
|
|
34
36
|
}
|
|
@@ -43,10 +45,11 @@ describe('KALEIDOSWAP_TOOLS — shape invariants', () => {
|
|
|
43
45
|
it('lists every spend tool exactly once in KALEIDOSWAP_SPEND_TOOLS', () => {
|
|
44
46
|
const expected = KALEIDOSWAP_TOOLS.filter((t) => t.spend).map((t) => t.name).sort();
|
|
45
47
|
expect([...KALEIDOSWAP_SPEND_TOOLS].sort()).toEqual(expected);
|
|
46
|
-
// Sanity: place_order, atomic_init,
|
|
48
|
+
// Sanity: place_order, atomic_init/execute, create_asset_channel are spend; the rest aren't.
|
|
47
49
|
expect(expected).toEqual([
|
|
48
50
|
'kaleidoswap_atomic_execute',
|
|
49
51
|
'kaleidoswap_atomic_init',
|
|
52
|
+
'kaleidoswap_lsp_create_asset_channel',
|
|
50
53
|
'kaleidoswap_place_order',
|
|
51
54
|
]);
|
|
52
55
|
});
|
|
@@ -95,6 +98,8 @@ describe('bindKaleidoswapTools', () => {
|
|
|
95
98
|
kaleidoswap_atomic_init: async (a) => ({ ok: true, tool: 'atomic_init', args: a }),
|
|
96
99
|
kaleidoswap_atomic_execute: async (a) => ({ ok: true, tool: 'atomic_execute', args: a }),
|
|
97
100
|
kaleidoswap_atomic_status: async (a) => ({ ok: true, tool: 'atomic_status', args: a }),
|
|
101
|
+
kaleidoswap_lsp_quote_asset_channel: async (a) => ({ ok: true, tool: 'lsp_quote_asset_channel', args: a }),
|
|
102
|
+
kaleidoswap_lsp_create_asset_channel: async (a) => ({ ok: true, tool: 'lsp_create_asset_channel', args: a }),
|
|
98
103
|
});
|
|
99
104
|
|
|
100
105
|
it('binds every tool when all handlers are present', () => {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* - eval → stub handlers, also via `bindKaleidoswapTools`
|
|
10
10
|
*
|
|
11
11
|
* Because the schemas are identical everywhere, skills are portable and the
|
|
12
|
-
* model comparison is honest. Tools are grouped (`market`, `orders`, `atomic
|
|
13
|
-
* so a host can expose a read-only subset for sandbox/eval modes.
|
|
12
|
+
* model comparison is honest. Tools are grouped (`market`, `orders`, `atomic`,
|
|
13
|
+
* `liquidity`) so a host can expose a read-only subset for sandbox/eval modes.
|
|
14
14
|
*
|
|
15
15
|
* Spend tools (place an order, init/execute an atomic swap) carry
|
|
16
16
|
* `spend: true` → `requiresConfirmation: true`, so the Engine always pauses
|
|
@@ -24,7 +24,7 @@ import { InProcessToolSource } from '../tools/in-process.js';
|
|
|
24
24
|
import type { InProcessTool } from '../tools/in-process.js';
|
|
25
25
|
|
|
26
26
|
/** Functional grouping for selective binding (e.g. read-only sandbox). */
|
|
27
|
-
export type KaleidoswapGroup = 'market' | 'orders' | 'atomic';
|
|
27
|
+
export type KaleidoswapGroup = 'market' | 'orders' | 'atomic' | 'liquidity';
|
|
28
28
|
|
|
29
29
|
export interface KaleidoswapToolDef extends ToolDef {
|
|
30
30
|
/** Functional group — lets a host expose a subset. */
|
|
@@ -86,7 +86,7 @@ export const KALEIDOSWAP_TOOLS: KaleidoswapToolDef[] = [
|
|
|
86
86
|
// ─── orders (orderbook / market-order flow) ─────────────────────────────
|
|
87
87
|
t('orders',
|
|
88
88
|
'kaleidoswap_place_order',
|
|
89
|
-
'Place an order using an executable quote. Returns
|
|
89
|
+
'Place an order using an executable quote. Returns order_id + access_token (save the token — required for get_order_status). SPEND: the host pauses for user confirmation before the maker is called. Use only after kaleidoswap_get_quote and only when the user has explicitly approved the amount + destination.',
|
|
90
90
|
{
|
|
91
91
|
quote_id: { type: 'string', description: 'The quote id returned by kaleidoswap_get_quote (must still be valid).' },
|
|
92
92
|
},
|
|
@@ -95,9 +95,10 @@ export const KALEIDOSWAP_TOOLS: KaleidoswapToolDef[] = [
|
|
|
95
95
|
|
|
96
96
|
t('orders',
|
|
97
97
|
'kaleidoswap_get_order_status',
|
|
98
|
-
'Check the status of an order by id — pending / settling / completed / failed. Poll this after place_order until the order settles.',
|
|
98
|
+
'Check the status of an order by id — pending / settling / completed / failed. Poll this after place_order until the order settles. Requires the access_token returned by place_order for authenticated orders.',
|
|
99
99
|
{
|
|
100
100
|
order_id: { type: 'string', description: 'The order id returned by kaleidoswap_place_order.' },
|
|
101
|
+
access_token: { type: 'string', description: 'The per-order access token returned by kaleidoswap_place_order. Required for status checks on the order.' },
|
|
101
102
|
},
|
|
102
103
|
['order_id']),
|
|
103
104
|
|
|
@@ -136,6 +137,27 @@ export const KALEIDOSWAP_TOOLS: KaleidoswapToolDef[] = [
|
|
|
136
137
|
atomic_id: { type: 'string', description: 'The atomic id from kaleidoswap_atomic_init.' },
|
|
137
138
|
},
|
|
138
139
|
['atomic_id']),
|
|
140
|
+
|
|
141
|
+
// ─── liquidity (buy a NEW channel pre-loaded with an asset — onboarding) ──
|
|
142
|
+
t('liquidity',
|
|
143
|
+
'kaleidoswap_lsp_quote_asset_channel',
|
|
144
|
+
'Quote buying a NEW Lightning channel pre-loaded with an RGB asset (e.g. USDT, XAUT) from the maker LSP. This is the onboarding path for a user who has on-chain BTC but no channel yet and wants to hold an asset — they pay once to receive a channel that already holds the asset. Read-only: returns an rfq_id, the BTC price in sats, the channel/setup fee, the total to pay, and when the quote expires. Re-quote rather than reusing a stale rfq_id.',
|
|
145
|
+
{
|
|
146
|
+
asset: { type: 'string', description: 'RGB asset to receive in the channel, e.g. "USDT" or "XAUT".' },
|
|
147
|
+
asset_amount: { type: 'number', description: 'How much of the asset to load into the channel, in the asset’s display units (e.g. 100 for 100 USDT).' },
|
|
148
|
+
},
|
|
149
|
+
['asset', 'asset_amount']),
|
|
150
|
+
|
|
151
|
+
t('liquidity',
|
|
152
|
+
'kaleidoswap_lsp_create_asset_channel',
|
|
153
|
+
'Order a new Lightning channel pre-loaded with an RGB asset from the maker LSP, using a fresh rfq_id from kaleidoswap_lsp_quote_asset_channel. SPEND: confirmation-gated. Returns an order id and the payment (on-chain address or Lightning invoice) the user pays to open the channel; the channel opens only after the payment confirms. Poll kaleidoswap_lsp_get_order to track it.',
|
|
154
|
+
{
|
|
155
|
+
asset: { type: 'string', description: 'RGB asset to receive (must match the quote).' },
|
|
156
|
+
asset_amount: { type: 'number', description: 'Asset amount in display units (must match the quote).' },
|
|
157
|
+
rfq_id: { type: 'string', description: 'The rfq_id from kaleidoswap_lsp_quote_asset_channel (must still be valid).' },
|
|
158
|
+
},
|
|
159
|
+
['asset', 'asset_amount', 'rfq_id'],
|
|
160
|
+
/* spend */ true),
|
|
139
161
|
];
|
|
140
162
|
|
|
141
163
|
/** All tool names that move funds (confirmation-gated). */
|
|
@@ -174,4 +174,115 @@ export const BITCOIN_COPILOT_DOCS: RagDocument[] = [
|
|
|
174
174
|
'accumulate BTC or an asset over time without timing the market.',
|
|
175
175
|
metadata: { topic: 'trading' },
|
|
176
176
|
},
|
|
177
|
+
{
|
|
178
|
+
id: 'spend-vs-receive-capacity',
|
|
179
|
+
text:
|
|
180
|
+
'Two completely different numbers, often confused: your SPEND capacity ' +
|
|
181
|
+
'(outbound, local balance — what you can send right now) and your ' +
|
|
182
|
+
'RECEIVE capacity (inbound, remote balance — what others can pay you ' +
|
|
183
|
+
'without opening a new channel). Knowing local_balance does NOT tell ' +
|
|
184
|
+
'you receive capacity, and vice versa. "How much can I spend?" → local ' +
|
|
185
|
+
'balance. "How much can I receive?" → inbound, derived from channels ' +
|
|
186
|
+
'or bought from an LSP.',
|
|
187
|
+
metadata: { topic: 'liquidity' },
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: 'nodeinfo-fields',
|
|
191
|
+
text:
|
|
192
|
+
'Common RGB Lightning Node fields and what they actually mean: pubkey ' +
|
|
193
|
+
'(your node identity); num_channels (total channels, including unusable ' +
|
|
194
|
+
'ones); num_usable_channels (subset that can route — what you spend ' +
|
|
195
|
+
'with); local_balance_sat (sats YOU own across all channels — your ' +
|
|
196
|
+
'spend / outbound capacity); pending_outbound_payments_sat (in-flight, ' +
|
|
197
|
+
'temporarily locked); eventual_close_fees_sat (cost if you close every ' +
|
|
198
|
+
'channel now); num_peers (connected peers). local_balance_sat is NOT ' +
|
|
199
|
+
'receive capacity and NOT total channel capacity.',
|
|
200
|
+
metadata: { topic: 'lightning' },
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: 'channel-two-sided',
|
|
204
|
+
text:
|
|
205
|
+
'Every Lightning channel has TWO balances: your side (local — what you ' +
|
|
206
|
+
'can spend) and the peer\'s side (remote — what they can spend, which ' +
|
|
207
|
+
'is what YOU can receive). Total channel capacity = local + remote and ' +
|
|
208
|
+
'is fixed at open time. Routing a payment moves sats from one side to ' +
|
|
209
|
+
'the other; it does NOT change total capacity. So if you "have 2 ' +
|
|
210
|
+
'channels with 1,000,000 sats total capacity", that does NOT mean you ' +
|
|
211
|
+
'can spend 1M and receive 1M — only the split tells you.',
|
|
212
|
+
metadata: { topic: 'channels' },
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
id: 'lsp-info-meaning',
|
|
216
|
+
text:
|
|
217
|
+
'The LSPS1 `get_info` endpoint returns the LSP\'s OFFER (min/max ' +
|
|
218
|
+
'channel size you can buy, fees, accepted payment options). It is NOT ' +
|
|
219
|
+
'your current inbound capacity — it describes what the LSP is willing ' +
|
|
220
|
+
'to sell you. To learn your CURRENT receive capacity, sum the remote ' +
|
|
221
|
+
'balance of your existing channels; to BUY MORE, use lsp_get_info and ' +
|
|
222
|
+
'lsp_create_order.',
|
|
223
|
+
metadata: { topic: 'channels' },
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
id: 'asset-channels',
|
|
227
|
+
text:
|
|
228
|
+
'RGB asset channels (colored channels) carry one specific asset like ' +
|
|
229
|
+
'USDT or XAUT alongside the BTC funding. Asset capacity is SEPARATE per ' +
|
|
230
|
+
'asset — having 100,000 sats spendable in BTC channels does not give ' +
|
|
231
|
+
'you USDT spendable; you need a USDT channel (or buy one via LSPS1). ' +
|
|
232
|
+
'Likewise, USDT inbound and BTC inbound are independent numbers.',
|
|
233
|
+
metadata: { topic: 'rgb' },
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: 'swap-vs-payment',
|
|
237
|
+
text:
|
|
238
|
+
'Swap and payment are different actions. A SWAP trades one asset for ' +
|
|
239
|
+
'another via the KaleidoSwap maker (quote → init → execute). A PAYMENT ' +
|
|
240
|
+
'moves an existing balance to a recipient over Lightning or on-chain ' +
|
|
241
|
+
'(no maker). "Send 10 USDT to Alice" is a payment; "swap 10 USDT to ' +
|
|
242
|
+
'BTC" is a swap. They use different tools, different fees, and a swap ' +
|
|
243
|
+
'is always between two assets the maker prices.',
|
|
244
|
+
metadata: { topic: 'usage' },
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: 'asset-channel-prereq',
|
|
248
|
+
text:
|
|
249
|
+
'Why you must buy a channel BEFORE swapping an RGB asset. An RGB ' +
|
|
250
|
+
'Lightning swap moves an asset (USDT, XAUT, …) across a channel that ' +
|
|
251
|
+
'already carries that asset. If you have no USDT channel, the maker ' +
|
|
252
|
+
'cannot pay you USDT over Lightning — there is no rail to push it ' +
|
|
253
|
+
"down. Open a USDT channel from the LSP first (LSPS1 with `asset_id`, " +
|
|
254
|
+
'`lsp_asset_amount`) — the LSP funds the asset on their side, you get ' +
|
|
255
|
+
'inbound USDT capacity, and afterwards a BTC→USDT swap can settle into ' +
|
|
256
|
+
'that channel. Same for XAUT or any other RGB asset. You need ONE ' +
|
|
257
|
+
'channel per asset you want to receive over Lightning.',
|
|
258
|
+
metadata: { topic: 'rgb-channels' },
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: 'asset-channel-buy',
|
|
262
|
+
text:
|
|
263
|
+
"Buying a channel that already has an asset inside. Use LSPS1 with " +
|
|
264
|
+
"`asset_id` to ask the LSP to open a channel that's pre-funded on the " +
|
|
265
|
+
"LSP side with a specific RGB asset. `lsp_asset_amount` is the asset " +
|
|
266
|
+
"units the LSP commits on their side (your future inbound capacity " +
|
|
267
|
+
"in that asset). `lsp_balance_sat` is the sats the LSP commits for " +
|
|
268
|
+
"fees/anchor; `client_balance_sat` is what you push in sats. Common " +
|
|
269
|
+
"shape: lsp_balance_sat 5_000_000, client_balance_sat 100_000, " +
|
|
270
|
+
"asset_id <USDT id>, lsp_asset_amount 100_000_000 micro-USDT (= 100 " +
|
|
271
|
+
"USDT). Pay the resulting Lightning invoice and the channel opens " +
|
|
272
|
+
"with the asset pre-loaded.",
|
|
273
|
+
metadata: { topic: 'rgb-channels' },
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: 'asset-channel-with-push',
|
|
277
|
+
text:
|
|
278
|
+
"Receiving an asset balance ON your side at channel open. Beyond the " +
|
|
279
|
+
"LSP-funded asset, you can request the LSP push some asset balance to " +
|
|
280
|
+
"YOUR side during the open via `client_asset_amount`. This costs sats " +
|
|
281
|
+
"(BTC → asset at the maker rate), so the maker requires a fresh " +
|
|
282
|
+
"`rfq_id` from `kaleidoswap_get_quote(BTC → asset)` to lock the " +
|
|
283
|
+
"price. The order then charges the BTC equivalent on top of the " +
|
|
284
|
+
"channel fee. Use when you want spendable asset balance immediately " +
|
|
285
|
+
"(not just inbound capacity).",
|
|
286
|
+
metadata: { topic: 'rgb-channels' },
|
|
287
|
+
},
|
|
177
288
|
];
|