@kaleidorg/mind 0.2.0 → 0.4.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/qvac/assistant.d.ts +73 -0
- package/dist/qvac/assistant.d.ts.map +1 -0
- package/dist/qvac/assistant.js +97 -0
- package/dist/qvac/assistant.js.map +1 -0
- package/dist/qvac/config.d.ts +64 -0
- package/dist/qvac/config.d.ts.map +1 -0
- package/dist/qvac/config.js +71 -0
- package/dist/qvac/config.js.map +1 -0
- package/dist/qvac/delegate.d.ts +48 -0
- package/dist/qvac/delegate.d.ts.map +1 -0
- package/dist/qvac/delegate.js +51 -0
- package/dist/qvac/delegate.js.map +1 -0
- package/dist/qvac/index.d.ts +19 -0
- package/dist/qvac/index.d.ts.map +1 -0
- package/dist/qvac/index.js +19 -0
- package/dist/qvac/index.js.map +1 -0
- package/dist/qvac/parse.d.ts +44 -0
- package/dist/qvac/parse.d.ts.map +1 -0
- package/dist/qvac/parse.js +28 -0
- package/dist/qvac/parse.js.map +1 -0
- package/dist/qvac/provider.d.ts +49 -0
- package/dist/qvac/provider.d.ts.map +1 -0
- package/dist/qvac/provider.js +68 -0
- package/dist/qvac/provider.js.map +1 -0
- package/dist/qvac/stream.d.ts +37 -0
- package/dist/qvac/stream.d.ts.map +1 -0
- package/dist/qvac/stream.js +29 -0
- package/dist/qvac/stream.js.map +1 -0
- package/dist/qvac/text.d.ts +19 -0
- package/dist/qvac/text.d.ts.map +1 -0
- package/dist/qvac/text.js +56 -0
- package/dist/qvac/text.js.map +1 -0
- package/dist/qvac/voice.d.ts +69 -0
- package/dist/qvac/voice.d.ts.map +1 -0
- package/dist/qvac/voice.js +51 -0
- package/dist/qvac/voice.js.map +1 -0
- 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 +16 -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/qvac/assistant.test.ts +132 -0
- package/src/qvac/assistant.ts +146 -0
- package/src/qvac/config.test.ts +44 -0
- package/src/qvac/config.ts +76 -0
- package/src/qvac/delegate.test.ts +68 -0
- package/src/qvac/delegate.ts +71 -0
- package/src/qvac/index.ts +72 -0
- package/src/qvac/parse.test.ts +52 -0
- package/src/qvac/parse.ts +57 -0
- package/src/qvac/provider.test.ts +107 -0
- package/src/qvac/provider.ts +124 -0
- package/src/qvac/stream.test.ts +79 -0
- package/src/qvac/stream.ts +56 -0
- package/src/qvac/text.test.ts +70 -0
- package/src/qvac/text.ts +60 -0
- package/src/qvac/voice.test.ts +151 -0
- package/src/qvac/voice.ts +122 -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
package/src/skills/registry.ts
CHANGED
|
@@ -75,6 +75,23 @@ const STOPWORDS = new Set([
|
|
|
75
75
|
'show', 'tell', 'how', 'much', 'many', 'about', 'into', 'over',
|
|
76
76
|
]);
|
|
77
77
|
|
|
78
|
+
/** Escape a string for safe inclusion in a regex. */
|
|
79
|
+
function reEscape(s: string): string {
|
|
80
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Match a trigger phrase in the query with WORD BOUNDARIES — so the
|
|
85
|
+
* `usd` trigger on a wallet skill doesn't fire on `usdt`/`usdc`, and
|
|
86
|
+
* `cafe` doesn't fire on `cafeteria`. Multi-word triggers ("near me")
|
|
87
|
+
* still work because spaces are already word boundaries.
|
|
88
|
+
*/
|
|
89
|
+
function triggerMatches(query: string, trigger: string): boolean {
|
|
90
|
+
const t = trigger.toLowerCase().trim();
|
|
91
|
+
if (!t) return false;
|
|
92
|
+
return new RegExp(`\\b${reEscape(t)}\\b`).test(query);
|
|
93
|
+
}
|
|
94
|
+
|
|
78
95
|
/** Default selector: score by meaningful keyword overlap; triggers weigh most. */
|
|
79
96
|
export const keywordSelector: SkillSelector = {
|
|
80
97
|
select(query, skills) {
|
|
@@ -89,8 +106,10 @@ export const keywordSelector: SkillSelector = {
|
|
|
89
106
|
const hayWords = haystack.split(/\W+/).filter((w) => w.length > 2 && !STOPWORDS.has(w));
|
|
90
107
|
let score = 0;
|
|
91
108
|
for (const w of hayWords) if (words.has(w)) score += 1;
|
|
92
|
-
// Strong boost for an explicit trigger appearing in the query
|
|
93
|
-
|
|
109
|
+
// Strong boost for an explicit trigger appearing in the query — at a
|
|
110
|
+
// word boundary, so short triggers (`usd`, `eur`, `cafe`) don't leak
|
|
111
|
+
// into longer words (`usdt`, `europe`, `cafeteria`).
|
|
112
|
+
for (const t of skill.triggers ?? []) if (triggerMatches(q, t)) score += 3;
|
|
94
113
|
if (score > bestScore) {
|
|
95
114
|
bestScore = score;
|
|
96
115
|
best = skill;
|
|
@@ -75,6 +75,48 @@ Manage Lightning channels via LSPS1.`);
|
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
describe('SkillRegistry selection — trigger word boundaries', () => {
|
|
79
|
+
// Regression: short triggers must NOT match inside longer words.
|
|
80
|
+
// Bug observed in the CLI: a wallet skill with `usd` as a trigger was
|
|
81
|
+
// picked for "what is the quote of usdt to btc" because the old
|
|
82
|
+
// q.includes("usd") was true for "usdt", outranking the trading skill.
|
|
83
|
+
const reg = new SkillRegistry();
|
|
84
|
+
reg.addMarkdown(`---
|
|
85
|
+
name: wallet-fiat
|
|
86
|
+
description: Check the BTC price and convert fiat to sats. Fiat support: usd, eur, gbp.
|
|
87
|
+
triggers: price, eur, usd, gbp
|
|
88
|
+
---
|
|
89
|
+
Wallet — fiat conversion.`);
|
|
90
|
+
reg.addMarkdown(`---
|
|
91
|
+
name: trading
|
|
92
|
+
description: Quote and execute swaps between BTC, USDT and XAUT on KaleidoSwap.
|
|
93
|
+
triggers: quote, swap, trade, usdt, xaut
|
|
94
|
+
---
|
|
95
|
+
Trading on the maker.`);
|
|
96
|
+
|
|
97
|
+
it("doesn't fire the `usd` trigger inside `usdt`", () => {
|
|
98
|
+
const sel = reg.select('what is the quote of usdt to btc')?.name;
|
|
99
|
+
expect(sel).toBe('trading'); // not wallet-fiat
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('still fires the `usd` trigger when the user actually said `usd`', () => {
|
|
103
|
+
const sel = reg.select('how many sats is 30 usd')?.name;
|
|
104
|
+
expect(sel).toBe('wallet-fiat');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("doesn't fire a short trigger inside a longer word — `cafe` not in `cafeteria`", () => {
|
|
108
|
+
const r2 = new SkillRegistry();
|
|
109
|
+
r2.addMarkdown(`---
|
|
110
|
+
name: merchants
|
|
111
|
+
description: Find merchants that accept Bitcoin.
|
|
112
|
+
triggers: cafe, restaurant, bar
|
|
113
|
+
---
|
|
114
|
+
Merchant finder.`);
|
|
115
|
+
expect(r2.select('the cafeteria menu')).toBeNull(); // no match
|
|
116
|
+
expect(r2.select('any bitcoin cafe nearby')?.name).toBe('merchants');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
78
120
|
describe('parseSkill — real Agent-Skills spec', () => {
|
|
79
121
|
it('unquotes the description, captures metadata, tolerates no tools', () => {
|
|
80
122
|
const s = parseSkill(BITREFILL_SKILL);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Confirm-sheet readback — deterministic, voice-first spend summaries. */
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect } from 'vitest';
|
|
4
|
+
import { confirmReadback } from './confirm.js';
|
|
5
|
+
|
|
6
|
+
describe('confirmReadback', () => {
|
|
7
|
+
it('send_payment: sats + recipient, grouped thousands', () => {
|
|
8
|
+
expect(confirmReadback({ name: 'send_payment', arguments: { to: 'bob', amount_sats: 4800 } }))
|
|
9
|
+
.toBe('Send 4,800 sats to bob. Confirm?');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('send_payment: explicit layer is read back', () => {
|
|
13
|
+
expect(confirmReadback({ name: 'send_payment', arguments: { to: 'bob', amount_sats: 1000000, layer: 'spark' } }))
|
|
14
|
+
.toBe('Send 1,000,000 sats to bob over Spark. Confirm?');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('send_payment: asset amount when no sats (core router, no layer suffix)', () => {
|
|
18
|
+
expect(confirmReadback({ name: 'send_payment', arguments: { to: 'alice', asset: 'USDT', amount: 10 } }))
|
|
19
|
+
.toBe('Send 10 USDT to alice. Confirm?');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('spark_send: layer comes from the tool, address is shortened', () => {
|
|
23
|
+
const line = confirmReadback({
|
|
24
|
+
name: 'spark_send',
|
|
25
|
+
arguments: { amount_sats: 5000, to: 'bc1qabcdef0123456789xyzlongaddress' },
|
|
26
|
+
});
|
|
27
|
+
expect(line).toBe('Send 5,000 sats to bc1qab…ress over Spark. Confirm?');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('rln_send_asset: asset + ticker + recipient over RLN', () => {
|
|
31
|
+
expect(confirmReadback({ name: 'rln_send_asset', arguments: { asset: 'USDT', amount: 10, to: 'bob' } }))
|
|
32
|
+
.toBe('Send 10 USDT to bob over RLN. Confirm?');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('rln_pay_invoice: invoice shortened, over RLN', () => {
|
|
36
|
+
const line = confirmReadback({
|
|
37
|
+
name: 'rln_pay_invoice',
|
|
38
|
+
arguments: { invoice: 'lnbc1ptestinvoice0123456789abcd' },
|
|
39
|
+
});
|
|
40
|
+
expect(line).toBe('Pay Lightning invoice lnbc1p…abcd over RLN. Confirm?');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('execute_swap: from → to with amount', () => {
|
|
44
|
+
expect(confirmReadback({ name: 'execute_swap', arguments: { from_asset: 'BTC', to_asset: 'USDT', amount: 0.01 } }))
|
|
45
|
+
.toBe('Swap 0.01 BTC for USDT. Confirm?');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('returns null for non-spend tools', () => {
|
|
49
|
+
expect(confirmReadback({ name: 'get_balances', arguments: {} })).toBeNull();
|
|
50
|
+
expect(confirmReadback({ name: 'resolve_contact', arguments: { name: 'bob' } })).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('short contact names are not truncated; long refs are', () => {
|
|
54
|
+
expect(confirmReadback({ name: 'arkade_send', arguments: { amount_sats: 100, to: 'mum' } }))
|
|
55
|
+
.toBe('Send 100 sats to mum over Arkade. Confirm?');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Confirm-sheet readback — a deterministic, voice-first summary of a spend that
|
|
3
|
+
* the host speaks before executing it ("Send 4,800 sats to bob over Spark.
|
|
4
|
+
* Confirm?"). Built from the resolved tool call, not the model: zero inference,
|
|
5
|
+
* identical on every surface, and impossible for the model to phrase around.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getWalletTool } from './contract.js';
|
|
9
|
+
|
|
10
|
+
const LAYER_LABEL: Record<string, string> = {
|
|
11
|
+
spark: 'Spark',
|
|
12
|
+
rln: 'RLN',
|
|
13
|
+
arkade: 'Arkade',
|
|
14
|
+
liquid: 'Liquid',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Group an integer with thousands separators, locale-independently (test-stable). */
|
|
18
|
+
function fmtNum(n: number): string {
|
|
19
|
+
if (!Number.isFinite(n)) return String(n);
|
|
20
|
+
const neg = n < 0;
|
|
21
|
+
const [int, frac] = Math.abs(n).toString().split('.');
|
|
22
|
+
const grouped = int!.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
23
|
+
return (neg ? '-' : '') + (frac ? `${grouped}.${frac}` : grouped);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Looks like an address/invoice/lnurl (vs a human contact name). */
|
|
27
|
+
function isRef(s: string): boolean {
|
|
28
|
+
return /^(ln(bc|tb|bcrt)|bc1|tb1|lq1|lnurl)/i.test(s) || (s.length > 20 && !/\s/.test(s));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Shorten an address/invoice for speech; leave contact names intact. */
|
|
32
|
+
function shortRef(s: string): string {
|
|
33
|
+
const v = s.trim();
|
|
34
|
+
return isRef(v) ? `${v.slice(0, 6)}…${v.slice(-4)}` : v;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** " over Spark" suffix for the call's layer (explicit arg wins, else the tool's). */
|
|
38
|
+
function over(name: string, args: Record<string, unknown>): string {
|
|
39
|
+
const layer = typeof args.layer === 'string' ? args.layer : getWalletTool(name)?.layer;
|
|
40
|
+
const label = layer ? LAYER_LABEL[layer] : undefined;
|
|
41
|
+
return label ? ` over ${label}` : '';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const sats = (v: unknown) => `${fmtNum(Number(v))} sats`;
|
|
45
|
+
const asset = (amount: unknown, ticker: unknown) => `${fmtNum(Number(amount))} ${String(ticker)}`;
|
|
46
|
+
|
|
47
|
+
/** A spoken confirmation ending in "Confirm?", or null for non-spend tools. */
|
|
48
|
+
export function confirmReadback(call: { name: string; arguments: Record<string, unknown> }): string | null {
|
|
49
|
+
const { name, arguments: a } = call;
|
|
50
|
+
const to = (k = 'to') => shortRef(String(a[k] ?? ''));
|
|
51
|
+
const ask = (s: string) => `${s}. Confirm?`;
|
|
52
|
+
|
|
53
|
+
switch (name) {
|
|
54
|
+
case 'send_payment': {
|
|
55
|
+
const amt = a.amount_sats != null ? sats(a.amount_sats)
|
|
56
|
+
: a.asset != null && a.amount != null ? asset(a.amount, a.asset)
|
|
57
|
+
: undefined;
|
|
58
|
+
return ask(amt ? `Send ${amt} to ${to()}${over(name, a)}` : `Send a payment to ${to()}${over(name, a)}`);
|
|
59
|
+
}
|
|
60
|
+
case 'spark_send':
|
|
61
|
+
case 'arkade_send':
|
|
62
|
+
return ask(`Send ${sats(a.amount_sats)} to ${to()}${over(name, a)}`);
|
|
63
|
+
case 'rln_send_asset':
|
|
64
|
+
case 'liquid_send':
|
|
65
|
+
return ask(`Send ${asset(a.amount, a.asset)} to ${to()}${over(name, a)}`);
|
|
66
|
+
case 'rln_pay_invoice':
|
|
67
|
+
return ask(`Pay Lightning invoice ${shortRef(String(a.invoice ?? ''))}${over(name, a)}`);
|
|
68
|
+
case 'execute_swap':
|
|
69
|
+
return ask(`Swap ${fmtNum(Number(a.amount))} ${String(a.from_asset)} for ${String(a.to_asset)}`);
|
|
70
|
+
default:
|
|
71
|
+
// Unknown but spend-flagged tool → a generic, still-honest readback.
|
|
72
|
+
return getWalletTool(name)?.spend ? ask(`Confirm ${name.replace(/_/g, ' ')}`) : null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: kaleido-wallet
|
|
3
|
-
description: "Manage a KaleidoSwap Lightning + RGB wallet: check BTC and asset balances, get a receive address, create or pay Lightning invoices, send on-chain BTC, open or list channels. Triggers when the user asks about their balance, wants to receive or send funds, pay an invoice, or manage Lightning channels."
|
|
4
|
-
tools: wdk_get_balances, wdk_get_asset_balance, wdk_get_address, wdk_create_ln_invoice, wdk_pay_invoice, wdk_send_btc, wdk_list_channels, wdk_open_channel, wdk_get_node_info
|
|
5
|
-
triggers: balance, receive, address, send, pay, invoice, channel, deposit, withdraw, funds
|
|
6
|
-
metadata:
|
|
7
|
-
author: kaleidoswap
|
|
8
|
-
version: "0.1.0"
|
|
9
|
-
surface: "kaleido-mcp (WDK node)"
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# KaleidoSwap wallet
|
|
13
|
-
|
|
14
|
-
Operate the user's KaleidoSwap node (Lightning + RGB assets) through the
|
|
15
|
-
`wdk_*` MCP tools.
|
|
16
|
-
|
|
17
|
-
## Rules
|
|
18
|
-
|
|
19
|
-
- **Read before you write.** Check the balance (`wdk_get_balances`) before any
|
|
20
|
-
send or channel open, and confirm the node is healthy (`wdk_get_node_info`).
|
|
21
|
-
- **Confirm every spend.** State the amount, destination, and resulting balance,
|
|
22
|
-
then wait for explicit approval before calling `wdk_send_btc`,
|
|
23
|
-
`wdk_pay_invoice`, or `wdk_open_channel`.
|
|
24
|
-
- **Match the rail to the asset.** Lightning for fast BTC/asset payments,
|
|
25
|
-
on-chain for settlement or channel funding. Use `wdk_get_asset_balance` for
|
|
26
|
-
RGB assets (USDT, XAUT, …).
|
|
27
|
-
- Never reveal seeds or private keys — this skill operates a node, it is not a
|
|
28
|
-
key vault.
|