@kaleidorg/mind 0.0.1 → 0.2.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 +34 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +34 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/context/budget.d.ts +29 -0
- package/dist/context/budget.d.ts.map +1 -0
- package/dist/context/budget.js +36 -0
- package/dist/context/budget.js.map +1 -0
- package/dist/context/builder.d.ts +39 -0
- package/dist/context/builder.d.ts.map +1 -0
- package/dist/context/builder.js +77 -0
- package/dist/context/builder.js.map +1 -0
- package/dist/engine.d.ts +9 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +18 -2
- package/dist/engine.js.map +1 -1
- package/dist/fastpath/fastpath.d.ts +38 -0
- package/dist/fastpath/fastpath.d.ts.map +1 -0
- package/dist/fastpath/fastpath.js +52 -0
- package/dist/fastpath/fastpath.js.map +1 -0
- package/dist/funnel.d.ts +111 -0
- package/dist/funnel.d.ts.map +1 -0
- package/dist/funnel.js +175 -0
- package/dist/funnel.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -1
- package/dist/knowledge/bitcoin-copilot.d.ts +11 -0
- package/dist/knowledge/bitcoin-copilot.d.ts.map +1 -0
- package/dist/knowledge/bitcoin-copilot.js +155 -0
- package/dist/knowledge/bitcoin-copilot.js.map +1 -0
- package/dist/knowledge/merchants.d.ts +24 -0
- package/dist/knowledge/merchants.d.ts.map +1 -0
- package/dist/knowledge/merchants.js +34 -0
- package/dist/knowledge/merchants.js.map +1 -0
- package/dist/knowledge/wallet.d.ts +34 -0
- package/dist/knowledge/wallet.d.ts.map +1 -0
- package/dist/knowledge/wallet.js +63 -0
- package/dist/knowledge/wallet.js.map +1 -0
- package/dist/memory/store.d.ts +34 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +103 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/memory/tool.d.ts +9 -0
- package/dist/memory/tool.d.ts.map +1 -0
- package/dist/memory/tool.js +70 -0
- package/dist/memory/tool.js.map +1 -0
- package/dist/memory/types.d.ts +56 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +14 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/rag/retriever.d.ts +30 -0
- package/dist/rag/retriever.d.ts.map +1 -0
- package/dist/rag/retriever.js +72 -0
- package/dist/rag/retriever.js.map +1 -0
- package/dist/rag/tool.d.ts +15 -0
- package/dist/rag/tool.d.ts.map +1 -0
- package/dist/rag/tool.js +42 -0
- package/dist/rag/tool.js.map +1 -0
- package/dist/rag/types.d.ts +44 -0
- package/dist/rag/types.d.ts.map +1 -0
- package/dist/rag/types.js +11 -0
- package/dist/rag/types.js.map +1 -0
- package/dist/rag/vector-store.d.ts +23 -0
- package/dist/rag/vector-store.d.ts.map +1 -0
- package/dist/rag/vector-store.js +72 -0
- package/dist/rag/vector-store.js.map +1 -0
- package/dist/recipe/asset-send.d.ts +15 -0
- package/dist/recipe/asset-send.d.ts.map +1 -0
- package/dist/recipe/asset-send.js +83 -0
- package/dist/recipe/asset-send.js.map +1 -0
- package/dist/recipe/payments.d.ts +15 -0
- package/dist/recipe/payments.d.ts.map +1 -0
- package/dist/recipe/payments.js +119 -0
- package/dist/recipe/payments.js.map +1 -0
- package/dist/recipe/receive.d.ts +14 -0
- package/dist/recipe/receive.d.ts.map +1 -0
- package/dist/recipe/receive.js +109 -0
- package/dist/recipe/receive.js.map +1 -0
- package/dist/recipe/runner.d.ts +42 -0
- package/dist/recipe/runner.d.ts.map +1 -0
- package/dist/recipe/runner.js +94 -0
- package/dist/recipe/runner.js.map +1 -0
- package/dist/recipe/swap.d.ts +16 -0
- package/dist/recipe/swap.d.ts.map +1 -0
- package/dist/recipe/swap.js +73 -0
- package/dist/recipe/swap.js.map +1 -0
- package/dist/recipe/types.d.ts +71 -0
- package/dist/recipe/types.d.ts.map +1 -0
- package/dist/recipe/types.js +13 -0
- package/dist/recipe/types.js.map +1 -0
- package/dist/skills/bundle.d.ts +30 -0
- package/dist/skills/bundle.d.ts.map +1 -0
- package/dist/skills/bundle.js +24 -0
- package/dist/skills/bundle.js.map +1 -0
- package/dist/skills/loader.d.ts +33 -0
- package/dist/skills/loader.d.ts.map +1 -0
- package/dist/skills/loader.js +59 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/skills/reference-source.d.ts +18 -0
- package/dist/skills/reference-source.d.ts.map +1 -0
- package/dist/skills/reference-source.js +53 -0
- package/dist/skills/reference-source.js.map +1 -0
- package/dist/skills/registry.d.ts +41 -0
- package/dist/skills/registry.d.ts.map +1 -0
- package/dist/skills/registry.js +167 -0
- package/dist/skills/registry.js.map +1 -0
- package/dist/skills/types.d.ts +53 -0
- package/dist/skills/types.d.ts.map +1 -0
- package/dist/skills/types.js +18 -0
- package/dist/skills/types.js.map +1 -0
- package/dist/tools/cli.d.ts +43 -0
- package/dist/tools/cli.d.ts.map +1 -0
- package/dist/tools/cli.js +61 -0
- package/dist/tools/cli.js.map +1 -0
- package/dist/tools/l402.d.ts +47 -0
- package/dist/tools/l402.d.ts.map +1 -0
- package/dist/tools/l402.js +84 -0
- package/dist/tools/l402.js.map +1 -0
- package/dist/tools/mcp.d.ts +3 -2
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/mcp.js +3 -2
- package/dist/tools/mcp.js.map +1 -1
- package/dist/wallet/contract.d.ts +57 -0
- package/dist/wallet/contract.d.ts.map +1 -0
- package/dist/wallet/contract.js +113 -0
- package/dist/wallet/contract.js.map +1 -0
- package/package.json +16 -5
- package/scripts/bundle-skills.mjs +84 -0
- package/skills/README.md +74 -0
- package/skills/bitrefill/SKILL.md +66 -0
- package/skills/bitrefill/references/api.md +99 -0
- package/skills/bitrefill/references/browse.md +71 -0
- package/skills/bitrefill/references/capability-matrix.md +115 -0
- package/skills/bitrefill/references/cli-headless-auth.md +133 -0
- package/skills/bitrefill/references/cli.md +237 -0
- package/skills/bitrefill/references/host-openclaw.md +167 -0
- package/skills/bitrefill/references/mcp.md +150 -0
- package/skills/bitrefill/references/safeguards.md +138 -0
- package/skills/bitrefill/references/troubleshooting.md +182 -0
- package/skills/kaleido-trading/SKILL.md +31 -0
- package/skills/kaleido-wallet/SKILL.md +28 -0
- package/src/capabilities.ts +67 -0
- package/src/context/budget.ts +46 -0
- package/src/context/builder.ts +100 -0
- package/src/context/context.test.ts +83 -0
- package/src/engine.test.ts +204 -0
- package/src/engine.ts +27 -2
- package/src/fastpath/fastpath.test.ts +34 -0
- package/src/fastpath/fastpath.ts +70 -0
- package/src/funnel.test.ts +207 -0
- package/src/funnel.ts +260 -0
- package/src/index.ts +102 -0
- package/src/knowledge/bitcoin-copilot.ts +177 -0
- package/src/knowledge/knowledge.test.ts +63 -0
- package/src/knowledge/merchants.ts +49 -0
- package/src/knowledge/wallet.ts +84 -0
- package/src/memory/memory.test.ts +85 -0
- package/src/memory/store.ts +129 -0
- package/src/memory/tool.ts +76 -0
- package/src/memory/types.ts +63 -0
- package/src/rag/rag.test.ts +85 -0
- package/src/rag/retriever.ts +94 -0
- package/src/rag/tool.ts +55 -0
- package/src/rag/types.ts +49 -0
- package/src/rag/vector-store.ts +78 -0
- package/src/recipe/asset-send.ts +79 -0
- package/src/recipe/payments.ts +116 -0
- package/src/recipe/receive.ts +98 -0
- package/src/recipe/recipe.test.ts +193 -0
- package/src/recipe/runner.ts +122 -0
- package/src/recipe/swap.ts +74 -0
- package/src/recipe/types.ts +76 -0
- package/src/skills/bundle.ts +42 -0
- package/src/skills/loader.ts +63 -0
- package/src/skills/reference-source.ts +60 -0
- package/src/skills/registry.ts +183 -0
- package/src/skills/skills.test.ts +191 -0
- package/src/skills/types.ts +55 -0
- package/src/tools/cli.test.ts +53 -0
- package/src/tools/cli.ts +98 -0
- package/src/tools/l402.test.ts +113 -0
- package/src/tools/l402.ts +122 -0
- package/src/tools/mcp.ts +3 -2
- package/src/wallet/contract.test.ts +89 -0
- package/src/wallet/contract.ts +157 -0
- package/dist/providers/qvac.d.ts +0 -89
- package/dist/providers/qvac.d.ts.map +0 -1
- package/dist/providers/qvac.js +0 -150
- package/dist/providers/qvac.js.map +0 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bitcoin-copilot knowledge pack — a curated, on-brand corpus to RAG over so an
|
|
3
|
+
* on-device assistant can answer Bitcoin / Lightning / RGB / KaleidoSwap
|
|
4
|
+
* questions privately. Ship it, ingest it with a Retriever, done.
|
|
5
|
+
*
|
|
6
|
+
* Concise + accurate. Extend or replace with your own docs (BOLT specs, RGB
|
|
7
|
+
* docs, app help, FAQs) — the format is just `RagDocument[]`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { RagDocument } from '../rag/types.js';
|
|
11
|
+
|
|
12
|
+
export const BITCOIN_COPILOT_DOCS: RagDocument[] = [
|
|
13
|
+
{
|
|
14
|
+
id: 'inbound-liquidity',
|
|
15
|
+
text:
|
|
16
|
+
'To RECEIVE Lightning payments you need inbound liquidity — remote balance ' +
|
|
17
|
+
'on a channel pointing at you. Brand-new wallets have none, so they can ' +
|
|
18
|
+
'send but not receive. Get inbound liquidity by buying a channel from an ' +
|
|
19
|
+
'LSP, or by receiving an on-chain deposit and swapping it into a channel.',
|
|
20
|
+
metadata: { topic: 'liquidity' },
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'outbound-liquidity',
|
|
24
|
+
text:
|
|
25
|
+
'Outbound liquidity is your local balance on a channel — what you can ' +
|
|
26
|
+
'spend over Lightning. You get it by funding a channel yourself or being ' +
|
|
27
|
+
'pushed funds. If you can receive but not send, you lack outbound liquidity.',
|
|
28
|
+
metadata: { topic: 'liquidity' },
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'channel-basics',
|
|
32
|
+
text:
|
|
33
|
+
'A Lightning channel is a 2-of-2 multisig funding output shared by two ' +
|
|
34
|
+
'peers. It lets them send instant, low-fee payments off-chain by updating ' +
|
|
35
|
+
'who owns how much, without touching the blockchain until the channel closes.',
|
|
36
|
+
metadata: { topic: 'lightning' },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'onchain-vs-lightning',
|
|
40
|
+
text:
|
|
41
|
+
'On-chain Bitcoin transactions settle directly on the blockchain: final, ' +
|
|
42
|
+
'but slower and with miner fees. Lightning payments are instant and cheap ' +
|
|
43
|
+
'but require channels with liquidity. Use Lightning for spending, on-chain ' +
|
|
44
|
+
'for settlement and for funding channels.',
|
|
45
|
+
metadata: { topic: 'lightning' },
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'open-channel',
|
|
49
|
+
text:
|
|
50
|
+
'Opening a channel funds a 2-of-2 output on-chain; it confirms in one or ' +
|
|
51
|
+
'more blocks (or is usable immediately with 0-conf if the peer allows). ' +
|
|
52
|
+
'The funder gets outbound liquidity. To get inbound, buy a channel from an ' +
|
|
53
|
+
'LSP instead of opening your own.',
|
|
54
|
+
metadata: { topic: 'channels' },
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'lsp-lsps1',
|
|
58
|
+
text:
|
|
59
|
+
'An LSP (Lightning Service Provider) sells channels. With LSPS1 you place ' +
|
|
60
|
+
'a channel order: choose capacity and how much inbound liquidity you want, ' +
|
|
61
|
+
'pay the fee, and the LSP opens a channel to you — often 0-conf, so you can ' +
|
|
62
|
+
'receive right away. KaleidoSwap acts as an LSP.',
|
|
63
|
+
metadata: { topic: 'channels' },
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'atomic-swap',
|
|
67
|
+
text:
|
|
68
|
+
'An atomic (HTLC) swap exchanges two assets so that either both legs ' +
|
|
69
|
+
'happen or neither does — no counterparty can run off with your funds. ' +
|
|
70
|
+
'KaleidoSwap uses a 5-step HTLC taker flow for trustless swaps.',
|
|
71
|
+
metadata: { topic: 'swaps' },
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'submarine-swap',
|
|
75
|
+
text:
|
|
76
|
+
'A submarine swap moves value between on-chain Bitcoin and Lightning ' +
|
|
77
|
+
'atomically via an HTLC: send on-chain BTC and receive it on Lightning, or ' +
|
|
78
|
+
'vice-versa, with no custodian. Useful to refill inbound/outbound liquidity.',
|
|
79
|
+
metadata: { topic: 'swaps' },
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'rgb-assets',
|
|
83
|
+
text:
|
|
84
|
+
'RGB is a protocol for issuing assets — like USDT and XAUT — on top of ' +
|
|
85
|
+
'Bitcoin and Lightning. Validation is client-side, which keeps it private ' +
|
|
86
|
+
'and scalable. On KaleidoSwap you can hold and swap RGB assets.',
|
|
87
|
+
metadata: { topic: 'rgb' },
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'colored-channels',
|
|
91
|
+
text:
|
|
92
|
+
'A colored channel is a Lightning channel that also carries an RGB asset ' +
|
|
93
|
+
'balance, so you can send/receive USDT or XAUT over Lightning instantly. ' +
|
|
94
|
+
'Buying an asset channel from the LSP gives you inbound capacity for that asset.',
|
|
95
|
+
metadata: { topic: 'rgb' },
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: 'rgb-invoice',
|
|
99
|
+
text:
|
|
100
|
+
'An RGB invoice requests a specific asset and amount and includes a ' +
|
|
101
|
+
'blinded UTXO so the sender can transfer the asset privately. It differs ' +
|
|
102
|
+
'from a plain Lightning (BOLT11) invoice, which is for BTC.',
|
|
103
|
+
metadata: { topic: 'rgb' },
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'rfq-quote',
|
|
107
|
+
text:
|
|
108
|
+
'Before a swap, KaleidoSwap gives a quote via RFQ (request for quote): the ' +
|
|
109
|
+
'maker prices the pair (e.g. BTC/USDT) and returns the amount you will ' +
|
|
110
|
+
'receive and the fees. Quotes expire — re-quote if you wait.',
|
|
111
|
+
metadata: { topic: 'trading' },
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'maker-taker',
|
|
115
|
+
text:
|
|
116
|
+
'KaleidoSwap is maker-based: a maker provides liquidity and prices, you ' +
|
|
117
|
+
'are the taker who accepts a quote and executes the atomic swap. The maker ' +
|
|
118
|
+
'also runs the LSP that sells channels.',
|
|
119
|
+
metadata: { topic: 'trading' },
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: 'mpp',
|
|
123
|
+
text:
|
|
124
|
+
'Multi-path payments (MPP) split one Lightning payment across several ' +
|
|
125
|
+
'channels/routes so you can send more than any single channel allows. The ' +
|
|
126
|
+
'parts recombine at the destination atomically.',
|
|
127
|
+
metadata: { topic: 'lightning' },
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'lightning-fees',
|
|
131
|
+
text:
|
|
132
|
+
'Lightning fees are tiny: a base fee plus a proportional fee per hop, paid ' +
|
|
133
|
+
'to routing nodes. They are far smaller than on-chain miner fees, which is ' +
|
|
134
|
+
'why Lightning suits everyday spending.',
|
|
135
|
+
metadata: { topic: 'fees' },
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'zero-conf',
|
|
139
|
+
text:
|
|
140
|
+
'0-conf (zero-confirmation) means a channel is usable before its funding ' +
|
|
141
|
+
'transaction is mined. It relies on trusting the channel partner not to ' +
|
|
142
|
+
'double-spend; LSPs commonly offer it so you can receive instantly.',
|
|
143
|
+
metadata: { topic: 'channels' },
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'seed-backup',
|
|
147
|
+
text:
|
|
148
|
+
'Your seed phrase (12/24 words) controls your funds. Write it down offline ' +
|
|
149
|
+
'and never share or photograph it. A KaleidoSwap node also needs channel ' +
|
|
150
|
+
'state backups: losing them can mean losing funds in open channels.',
|
|
151
|
+
metadata: { topic: 'security' },
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: 'receiving',
|
|
155
|
+
text:
|
|
156
|
+
'To receive: share a Lightning invoice (BOLT11) or an on-chain address. ' +
|
|
157
|
+
'For Lightning you need inbound liquidity first. For RGB assets, share an ' +
|
|
158
|
+
'RGB invoice. On-chain always works but is slower.',
|
|
159
|
+
metadata: { topic: 'usage' },
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: 'sending',
|
|
163
|
+
text:
|
|
164
|
+
'To send: pay a Lightning invoice or a Lightning address for BTC, or use ' +
|
|
165
|
+
'an on-chain address. You need outbound liquidity for Lightning. Always ' +
|
|
166
|
+
'check the amount and destination before paying.',
|
|
167
|
+
metadata: { topic: 'usage' },
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'dca',
|
|
171
|
+
text:
|
|
172
|
+
'Dollar-cost averaging (DCA) buys a fixed amount on a schedule to smooth ' +
|
|
173
|
+
'out price swings. KaleidoSwap can automate recurring swaps so you ' +
|
|
174
|
+
'accumulate BTC or an asset over time without timing the market.',
|
|
175
|
+
metadata: { topic: 'trading' },
|
|
176
|
+
},
|
|
177
|
+
];
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Knowledge pack + corpus adapter tests — pure transforms. */
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect } from 'vitest';
|
|
4
|
+
import { BITCOIN_COPILOT_DOCS } from './bitcoin-copilot.js';
|
|
5
|
+
import { walletHistoryToDocuments, contactsToDocuments } from './wallet.js';
|
|
6
|
+
import { merchantsToDocuments } from './merchants.js';
|
|
7
|
+
|
|
8
|
+
describe('BITCOIN_COPILOT_DOCS', () => {
|
|
9
|
+
it('is a non-trivial corpus with unique ids and real text', () => {
|
|
10
|
+
expect(BITCOIN_COPILOT_DOCS.length).toBeGreaterThanOrEqual(15);
|
|
11
|
+
const ids = BITCOIN_COPILOT_DOCS.map((d) => d.id);
|
|
12
|
+
expect(new Set(ids).size).toBe(ids.length); // unique
|
|
13
|
+
expect(BITCOIN_COPILOT_DOCS.every((d) => (d.text?.length ?? 0) > 40)).toBe(true);
|
|
14
|
+
// covers the key concept the hero demo asks about
|
|
15
|
+
expect(BITCOIN_COPILOT_DOCS.some((d) => /inbound liquidity/i.test(d.text))).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('walletHistoryToDocuments', () => {
|
|
20
|
+
it('renders sent/received sentences with date, amount, counterparty', () => {
|
|
21
|
+
const docs = walletHistoryToDocuments([
|
|
22
|
+
{ id: 't1', type: 'send', amountSats: 5000, counterparty: 'Bob', memo: 'lunch', timestamp: 1700000000000 },
|
|
23
|
+
{ id: 't2', type: 'receive', amountSats: 21000, counterparty: 'Alice', timestamp: 1700100000000 },
|
|
24
|
+
{ type: 'swap', amountSats: 100000, asset: 'USDT' },
|
|
25
|
+
]);
|
|
26
|
+
expect(docs[0].text).toMatch(/you sent 5000 sats to Bob — "lunch"/);
|
|
27
|
+
expect(docs[1].text).toMatch(/you received 21000 sats from Alice/);
|
|
28
|
+
expect(docs[2].text).toMatch(/you swapped 100000 USDT/);
|
|
29
|
+
expect(docs[2].id).toBe('tx_2'); // id defaulted
|
|
30
|
+
expect(docs[0].metadata?.kind).toBe('transaction');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('contactsToDocuments', () => {
|
|
35
|
+
it('includes address/nostr/note and skips empty contacts', () => {
|
|
36
|
+
const docs = contactsToDocuments([
|
|
37
|
+
{ name: 'Bob', lightningAddress: 'bob@getalby.com', note: 'coffee buddy' },
|
|
38
|
+
{}, // skipped
|
|
39
|
+
]);
|
|
40
|
+
expect(docs).toHaveLength(1);
|
|
41
|
+
expect(docs[0].text).toMatch(/Contact: Bob — Lightning address bob@getalby.com, coffee buddy/);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('merchantsToDocuments', () => {
|
|
46
|
+
it('renders place + acceptance and preserves coordinates in metadata', () => {
|
|
47
|
+
const docs = merchantsToDocuments([
|
|
48
|
+
{
|
|
49
|
+
name: 'Bitcoin Café',
|
|
50
|
+
category: 'cafe',
|
|
51
|
+
address: 'Via Nassa 1',
|
|
52
|
+
city: 'Lugano',
|
|
53
|
+
lat: 46.0,
|
|
54
|
+
lng: 8.95,
|
|
55
|
+
acceptedAssets: ['lightning', 'onchain'],
|
|
56
|
+
},
|
|
57
|
+
{ city: 'nowhere' }, // no name → skipped
|
|
58
|
+
]);
|
|
59
|
+
expect(docs).toHaveLength(1);
|
|
60
|
+
expect(docs[0].text).toMatch(/Bitcoin Café \(cafe\) at Via Nassa 1, Lugano\. Accepts lightning, onchain\./);
|
|
61
|
+
expect(docs[0].metadata).toMatchObject({ kind: 'merchant', lat: 46.0, lng: 8.95, city: 'Lugano' });
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BTC map discovery — turn a merchant directory (e.g. BTCMap / a local dataset)
|
|
3
|
+
* into `RagDocument[]` so the agent can answer "where can I spend Bitcoin for
|
|
4
|
+
* coffee near me?" with on-device semantic search over places.
|
|
5
|
+
*
|
|
6
|
+
* Pure transform over a generic Merchant shape. Coordinates are kept in
|
|
7
|
+
* metadata so the host can still pin results on a map after retrieval.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { RagDocument } from '../rag/types.js';
|
|
11
|
+
|
|
12
|
+
export interface Merchant {
|
|
13
|
+
id?: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
category?: string; // 'cafe' | 'restaurant' | 'shop' | …
|
|
16
|
+
address?: string;
|
|
17
|
+
city?: string;
|
|
18
|
+
lat?: number;
|
|
19
|
+
lng?: number;
|
|
20
|
+
/** e.g. ['onchain', 'lightning', 'rgb']. */
|
|
21
|
+
acceptedAssets?: string[];
|
|
22
|
+
description?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** One searchable doc per merchant; lat/lng preserved in metadata for mapping. */
|
|
26
|
+
export function merchantsToDocuments(merchants: Merchant[]): RagDocument[] {
|
|
27
|
+
return merchants
|
|
28
|
+
.filter((m) => m.name)
|
|
29
|
+
.map((m, i) => {
|
|
30
|
+
const where = [m.address, m.city].filter(Boolean).join(', ');
|
|
31
|
+
const pays = m.acceptedAssets?.length
|
|
32
|
+
? `Accepts ${m.acceptedAssets.join(', ')}.`
|
|
33
|
+
: 'Accepts Bitcoin.';
|
|
34
|
+
const cat = m.category ? ` (${m.category})` : '';
|
|
35
|
+
const desc = m.description ? ` ${m.description}` : '';
|
|
36
|
+
return {
|
|
37
|
+
id: m.id ?? `merchant_${m.name ?? i}`,
|
|
38
|
+
text: `${m.name}${cat}${where ? ` at ${where}` : ''}. ${pays}${desc}`.trim(),
|
|
39
|
+
metadata: {
|
|
40
|
+
kind: 'merchant',
|
|
41
|
+
name: m.name,
|
|
42
|
+
category: m.category,
|
|
43
|
+
city: m.city,
|
|
44
|
+
lat: m.lat,
|
|
45
|
+
lng: m.lng,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Personal wallet knowledge — turn a user's transaction history + contacts into
|
|
3
|
+
* `RagDocument[]` so the agent can answer "what did I spend on coffee last
|
|
4
|
+
* month?", "who did I pay 50k sats to?", "summarise my swaps" — all on-device,
|
|
5
|
+
* nothing leaving the phone.
|
|
6
|
+
*
|
|
7
|
+
* Pure transforms over minimal, generic shapes (hosts map their own types in).
|
|
8
|
+
* No deps, no PII leaves: the host decides what to ingest.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { RagDocument } from '../rag/types.js';
|
|
12
|
+
|
|
13
|
+
export interface WalletTx {
|
|
14
|
+
id?: string;
|
|
15
|
+
/** 'send' | 'receive' | 'swap' | 'deposit' | 'withdraw' | … */
|
|
16
|
+
type?: string;
|
|
17
|
+
amountSats?: number;
|
|
18
|
+
asset?: string; // 'BTC' | 'USDT' | 'XAUT' | …
|
|
19
|
+
/** Who/where — a name, contact, address, or merchant. */
|
|
20
|
+
counterparty?: string;
|
|
21
|
+
memo?: string;
|
|
22
|
+
status?: string;
|
|
23
|
+
/** Epoch ms. */
|
|
24
|
+
timestamp?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Contact {
|
|
28
|
+
name?: string;
|
|
29
|
+
lightningAddress?: string;
|
|
30
|
+
npub?: string;
|
|
31
|
+
note?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isoDate(ts?: number): string {
|
|
35
|
+
if (!ts) return 'an unknown date';
|
|
36
|
+
// Avoid Date formatting differences — just YYYY-MM-DD from the ISO string.
|
|
37
|
+
try {
|
|
38
|
+
return new Date(ts).toISOString().slice(0, 10);
|
|
39
|
+
} catch {
|
|
40
|
+
return 'an unknown date';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** One short, searchable sentence per transaction. */
|
|
45
|
+
export function walletHistoryToDocuments(txs: WalletTx[]): RagDocument[] {
|
|
46
|
+
return txs.map((tx, i) => {
|
|
47
|
+
const date = isoDate(tx.timestamp);
|
|
48
|
+
const asset = tx.asset ?? 'sats';
|
|
49
|
+
const amount = tx.amountSats != null ? `${tx.amountSats} ${asset === 'BTC' ? 'sats' : asset}` : 'an amount';
|
|
50
|
+
const verb =
|
|
51
|
+
tx.type === 'receive' || tx.type === 'deposit'
|
|
52
|
+
? 'received'
|
|
53
|
+
: tx.type === 'swap'
|
|
54
|
+
? 'swapped'
|
|
55
|
+
: tx.type === 'withdraw'
|
|
56
|
+
? 'withdrew'
|
|
57
|
+
: 'sent';
|
|
58
|
+
const who = tx.counterparty ? ` ${verb === 'received' ? 'from' : 'to'} ${tx.counterparty}` : '';
|
|
59
|
+
const memo = tx.memo ? ` — "${tx.memo}"` : '';
|
|
60
|
+
const status = tx.status && tx.status !== 'complete' ? ` (${tx.status})` : '';
|
|
61
|
+
return {
|
|
62
|
+
id: tx.id ?? `tx_${i}`,
|
|
63
|
+
text: `On ${date} you ${verb} ${amount}${who}${memo}${status}.`,
|
|
64
|
+
metadata: { kind: 'transaction', type: tx.type, asset: tx.asset, timestamp: tx.timestamp },
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** One doc per contact, so "who is Bob?" / "pay my friend" can resolve. */
|
|
70
|
+
export function contactsToDocuments(contacts: Contact[]): RagDocument[] {
|
|
71
|
+
return contacts
|
|
72
|
+
.filter((c) => c.name || c.lightningAddress || c.npub)
|
|
73
|
+
.map((c, i) => {
|
|
74
|
+
const parts: string[] = [];
|
|
75
|
+
if (c.lightningAddress) parts.push(`Lightning address ${c.lightningAddress}`);
|
|
76
|
+
if (c.npub) parts.push(`Nostr ${c.npub}`);
|
|
77
|
+
if (c.note) parts.push(c.note);
|
|
78
|
+
return {
|
|
79
|
+
id: `contact_${c.name ?? c.lightningAddress ?? i}`,
|
|
80
|
+
text: `Contact: ${c.name ?? 'unnamed'}${parts.length ? ` — ${parts.join(', ')}` : ''}.`,
|
|
81
|
+
metadata: { kind: 'contact', name: c.name },
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** Memory store + tool tests — deterministic, no embeddings needed. */
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
4
|
+
import { InMemoryMemoryStore } from './store.js';
|
|
5
|
+
import { createMemoryToolSource } from './tool.js';
|
|
6
|
+
import type { MemoryItem, MemoryIO } from './types.js';
|
|
7
|
+
|
|
8
|
+
describe('InMemoryMemoryStore', () => {
|
|
9
|
+
it('adds and recalls by recency when no query text', async () => {
|
|
10
|
+
let t = 1000;
|
|
11
|
+
const store = new InMemoryMemoryStore({ now: () => t++ });
|
|
12
|
+
await store.add({ text: 'first', kind: 'note' });
|
|
13
|
+
await store.add({ text: 'second', kind: 'note' });
|
|
14
|
+
const all = await store.search({ limit: 5 });
|
|
15
|
+
expect(all.map((m) => m.text)).toEqual(['second', 'first']); // newest first
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('filters by kind + tags, ranks substring hits first', async () => {
|
|
19
|
+
let t = 0;
|
|
20
|
+
const store = new InMemoryMemoryStore({ now: () => ++t });
|
|
21
|
+
await store.add({ text: 'likes cold brew', kind: 'preference', tags: ['coffee'] });
|
|
22
|
+
await store.add({ text: 'paid rent', kind: 'event' });
|
|
23
|
+
await store.add({ text: 'prefers dark mode', kind: 'preference', tags: ['ui'] });
|
|
24
|
+
|
|
25
|
+
const prefs = await store.search({ kind: 'preference', limit: 5 });
|
|
26
|
+
expect(prefs).toHaveLength(2);
|
|
27
|
+
|
|
28
|
+
const coffee = await store.search({ tags: ['coffee'], limit: 5 });
|
|
29
|
+
expect(coffee.map((m) => m.text)).toEqual(['likes cold brew']);
|
|
30
|
+
|
|
31
|
+
const hit = await store.search({ text: 'dark', limit: 5 });
|
|
32
|
+
expect(hit[0].text).toBe('prefers dark mode');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('persists through injected IO (load + save)', async () => {
|
|
36
|
+
const saved: MemoryItem[] = [];
|
|
37
|
+
const io: MemoryIO = {
|
|
38
|
+
load: vi.fn(async () => [...saved]),
|
|
39
|
+
save: vi.fn(async (items) => {
|
|
40
|
+
saved.length = 0;
|
|
41
|
+
saved.push(...items);
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
const store = new InMemoryMemoryStore({ io, now: () => 1 });
|
|
45
|
+
await store.add({ text: 'remember me', kind: 'fact' });
|
|
46
|
+
expect(io.save).toHaveBeenCalled();
|
|
47
|
+
expect(saved).toHaveLength(1);
|
|
48
|
+
|
|
49
|
+
// A fresh store hydrates from the same IO.
|
|
50
|
+
const store2 = new InMemoryMemoryStore({ io, now: () => 2 });
|
|
51
|
+
expect((await store2.all())[0].text).toBe('remember me');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('semantic recall when an embedder is wired', async () => {
|
|
55
|
+
// 2-dim embeddings: dimension 0 = "wallet"-ness, 1 = "weather"-ness.
|
|
56
|
+
const embed = async (text: string): Promise<number[]> =>
|
|
57
|
+
/balance|wallet|sats/i.test(text) ? [1, 0] : [0, 1];
|
|
58
|
+
const store = new InMemoryMemoryStore({ embed, now: () => 1 });
|
|
59
|
+
await store.add({ text: 'user wallet balance is low', kind: 'fact' });
|
|
60
|
+
await store.add({ text: 'it is sunny today', kind: 'note' });
|
|
61
|
+
const hits = await store.search({ text: 'how many sats do I have', limit: 1 });
|
|
62
|
+
expect(hits[0].text).toMatch(/wallet balance/);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('memory tool source', () => {
|
|
67
|
+
it('remember saves and recall returns matches', async () => {
|
|
68
|
+
const store = new InMemoryMemoryStore({ now: () => 1 });
|
|
69
|
+
const src = createMemoryToolSource(store);
|
|
70
|
+
expect(src.listTools().map((t) => t.name)).toEqual(['remember', 'recall']);
|
|
71
|
+
|
|
72
|
+
const saved = await src.execute('remember', { text: 'BTC only', kind: 'preference' });
|
|
73
|
+
expect(String(saved)).toMatch(/Remembered \(preference\)/);
|
|
74
|
+
|
|
75
|
+
const recalled = await src.execute('recall', { query: 'BTC' });
|
|
76
|
+
expect(String(recalled)).toMatch(/BTC only/);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('defaults an invalid kind to note', async () => {
|
|
80
|
+
const store = new InMemoryMemoryStore({ now: () => 1 });
|
|
81
|
+
const src = createMemoryToolSource(store);
|
|
82
|
+
await src.execute('remember', { text: 'x', kind: 'banana' });
|
|
83
|
+
expect((await store.all())[0].kind).toBe('note');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemoryStore implementation — in-memory, with optional injected persistence
|
|
3
|
+
* and optional semantic ranking. Pure TS, zero deps.
|
|
4
|
+
*
|
|
5
|
+
* const store = new InMemoryMemoryStore(); // ephemeral
|
|
6
|
+
* const store = new InMemoryMemoryStore({ io }); // persisted (RN/Node)
|
|
7
|
+
* const store = new InMemoryMemoryStore({ io, embed }); // + semantic recall
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { cosineSimilarity } from '../rag/vector-store.js';
|
|
11
|
+
import type {
|
|
12
|
+
MemoryIO,
|
|
13
|
+
MemoryItem,
|
|
14
|
+
MemoryQuery,
|
|
15
|
+
MemoryStore,
|
|
16
|
+
NewMemory,
|
|
17
|
+
} from './types.js';
|
|
18
|
+
|
|
19
|
+
export interface MemoryStoreOptions {
|
|
20
|
+
/** Persistence (load on first use, save on writes). Omit for ephemeral memory. */
|
|
21
|
+
io?: MemoryIO;
|
|
22
|
+
/** Embed text for semantic recall. Omit to fall back to substring matching. */
|
|
23
|
+
embed?: (text: string) => Promise<number[]>;
|
|
24
|
+
/** Clock — injectable for deterministic tests. */
|
|
25
|
+
now?: () => number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class InMemoryMemoryStore implements MemoryStore {
|
|
29
|
+
private items: MemoryItem[] = [];
|
|
30
|
+
private hydrated = false;
|
|
31
|
+
private counter = 0;
|
|
32
|
+
private readonly io?: MemoryIO;
|
|
33
|
+
private readonly embed?: (text: string) => Promise<number[]>;
|
|
34
|
+
private readonly now: () => number;
|
|
35
|
+
|
|
36
|
+
constructor(opts: MemoryStoreOptions = {}) {
|
|
37
|
+
this.io = opts.io;
|
|
38
|
+
this.embed = opts.embed;
|
|
39
|
+
this.now = opts.now ?? (() => Date.now());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private async hydrate(): Promise<void> {
|
|
43
|
+
if (this.hydrated) return;
|
|
44
|
+
this.hydrated = true;
|
|
45
|
+
if (this.io) {
|
|
46
|
+
try {
|
|
47
|
+
this.items = await this.io.load();
|
|
48
|
+
this.counter = this.items.length;
|
|
49
|
+
} catch {
|
|
50
|
+
this.items = [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private async persist(): Promise<void> {
|
|
56
|
+
if (this.io) await this.io.save(this.items);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async add(item: NewMemory): Promise<MemoryItem> {
|
|
60
|
+
await this.hydrate();
|
|
61
|
+
const embedding =
|
|
62
|
+
item.embedding ?? (this.embed ? await this.embed(item.text).catch(() => undefined) : undefined);
|
|
63
|
+
const full: MemoryItem = {
|
|
64
|
+
id: item.id ?? `mem_${this.now()}_${++this.counter}`,
|
|
65
|
+
text: item.text,
|
|
66
|
+
kind: item.kind,
|
|
67
|
+
tags: item.tags,
|
|
68
|
+
createdAt: item.createdAt ?? this.now(),
|
|
69
|
+
...(embedding ? { embedding } : {}),
|
|
70
|
+
};
|
|
71
|
+
this.items.push(full);
|
|
72
|
+
await this.persist();
|
|
73
|
+
return full;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async all(): Promise<MemoryItem[]> {
|
|
77
|
+
await this.hydrate();
|
|
78
|
+
return [...this.items];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async search(query: MemoryQuery): Promise<MemoryItem[]> {
|
|
82
|
+
await this.hydrate();
|
|
83
|
+
const limit = query.limit ?? 5;
|
|
84
|
+
|
|
85
|
+
let pool = this.items;
|
|
86
|
+
if (query.kind) pool = pool.filter((m) => m.kind === query.kind);
|
|
87
|
+
if (query.tags?.length) {
|
|
88
|
+
pool = pool.filter((m) => query.tags!.every((t) => m.tags?.includes(t)));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const text = query.text?.trim();
|
|
92
|
+
if (!text) {
|
|
93
|
+
// No query text → most recent first.
|
|
94
|
+
return [...pool].sort((a, b) => b.createdAt - a.createdAt).slice(0, limit);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Semantic ranking when both the query and items are embedded.
|
|
98
|
+
if (this.embed && pool.some((m) => m.embedding)) {
|
|
99
|
+
const qv = await this.embed(text).catch(() => null);
|
|
100
|
+
if (qv) {
|
|
101
|
+
return [...pool]
|
|
102
|
+
.map((m) => ({ m, score: m.embedding ? cosineSimilarity(qv, m.embedding) : -1 }))
|
|
103
|
+
.sort((a, b) => b.score - a.score)
|
|
104
|
+
.slice(0, limit)
|
|
105
|
+
.map((x) => x.m);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Fallback: substring score + recency.
|
|
110
|
+
const q = text.toLowerCase();
|
|
111
|
+
return [...pool]
|
|
112
|
+
.map((m) => ({ m, hit: m.text.toLowerCase().includes(q) ? 1 : 0 }))
|
|
113
|
+
.sort((a, b) => b.hit - a.hit || b.m.createdAt - a.m.createdAt)
|
|
114
|
+
.slice(0, limit)
|
|
115
|
+
.map((x) => x.m);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async remove(id: string): Promise<void> {
|
|
119
|
+
await this.hydrate();
|
|
120
|
+
this.items = this.items.filter((m) => m.id !== id);
|
|
121
|
+
await this.persist();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async clear(): Promise<void> {
|
|
125
|
+
await this.hydrate();
|
|
126
|
+
this.items = [];
|
|
127
|
+
await this.persist();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory tool source — lets the agent persist and recall things across
|
|
3
|
+
* sessions (`remember`, `recall`). Pairs with auto-recall in the
|
|
4
|
+
* ContextBuilder; this is the explicit, agent-driven side.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ToolDef } from '../types.js';
|
|
8
|
+
import type { ToolSource } from '../tools/source.js';
|
|
9
|
+
import type { MemoryKind, MemoryStore } from './types.js';
|
|
10
|
+
|
|
11
|
+
const REMEMBER = 'remember';
|
|
12
|
+
const RECALL = 'recall';
|
|
13
|
+
const KINDS: MemoryKind[] = ['fact', 'preference', 'event', 'note'];
|
|
14
|
+
|
|
15
|
+
export function createMemoryToolSource(store: MemoryStore): ToolSource {
|
|
16
|
+
const tools: ToolDef[] = [
|
|
17
|
+
{
|
|
18
|
+
name: REMEMBER,
|
|
19
|
+
description:
|
|
20
|
+
'Save something to long-term memory so you recall it in future sessions ' +
|
|
21
|
+
'— a user preference, a fact, or an event. Use sparingly for durable info.',
|
|
22
|
+
parameters: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
text: { type: 'string', description: 'What to remember (a short sentence)' },
|
|
26
|
+
kind: { type: 'string', enum: KINDS, description: 'fact | preference | event | note' },
|
|
27
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags' },
|
|
28
|
+
},
|
|
29
|
+
required: ['text'],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: RECALL,
|
|
34
|
+
description:
|
|
35
|
+
'Search your long-term memory for what you know about something before answering.',
|
|
36
|
+
parameters: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
query: { type: 'string', description: 'What to recall' },
|
|
40
|
+
limit: { type: 'number', description: 'Max items (default 5)' },
|
|
41
|
+
},
|
|
42
|
+
required: ['query'],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
async function execute(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
48
|
+
if (name === REMEMBER) {
|
|
49
|
+
const text = String(args.text ?? '').trim();
|
|
50
|
+
if (!text) throw new Error('remember: text is required');
|
|
51
|
+
const kind = (KINDS as string[]).includes(String(args.kind))
|
|
52
|
+
? (args.kind as MemoryKind)
|
|
53
|
+
: 'note';
|
|
54
|
+
const tags = Array.isArray(args.tags) ? args.tags.map(String) : undefined;
|
|
55
|
+
const item = await store.add({ text, kind, tags });
|
|
56
|
+
return `Remembered (${item.kind}): ${item.text}`;
|
|
57
|
+
}
|
|
58
|
+
if (name === RECALL) {
|
|
59
|
+
const query = String(args.query ?? '').trim();
|
|
60
|
+
if (!query) throw new Error('recall: query is required');
|
|
61
|
+
const limit = Number(args.limit) > 0 ? Number(args.limit) : 5;
|
|
62
|
+
const items = await store.search({ text: query, limit });
|
|
63
|
+
if (items.length === 0) return 'Nothing relevant in memory.';
|
|
64
|
+
return items.map((m) => `- (${m.kind}) ${m.text}`).join('\n');
|
|
65
|
+
}
|
|
66
|
+
throw new Error(`memory: unknown tool ${name}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const names = new Set([REMEMBER, RECALL]);
|
|
70
|
+
return {
|
|
71
|
+
id: 'memory',
|
|
72
|
+
listTools: () => tools,
|
|
73
|
+
has: (name) => names.has(name),
|
|
74
|
+
execute,
|
|
75
|
+
};
|
|
76
|
+
}
|