@kaleidorg/mind 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capabilities.d.ts +38 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +41 -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 +1 -0
- 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 +117 -0
- package/dist/funnel.d.ts.map +1 -0
- package/dist/funnel.js +195 -0
- package/dist/funnel.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -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/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/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/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/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 +40 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +143 -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 +68 -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/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/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 +106 -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/registry.d.ts.map +1 -1
- package/dist/skills/registry.js +20 -2
- package/dist/skills/registry.js.map +1 -1
- 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/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/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/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 +10 -5
- 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 +79 -0
- package/src/context/budget.ts +46 -0
- package/src/context/builder.ts +100 -0
- package/src/context/context.test.ts +87 -0
- package/src/engine.ts +6 -0
- 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 +285 -0
- package/src/index.ts +128 -0
- package/src/kaleidoswap/contract.test.ts +147 -0
- package/src/kaleidoswap/contract.ts +212 -0
- package/src/knowledge/bitcoin-copilot.ts +177 -0
- package/src/knowledge/btc-map.test.ts +188 -0
- package/src/knowledge/btc-map.ts +446 -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/lsps1/contract.test.ts +81 -0
- package/src/lsps1/contract.ts +132 -0
- package/src/memory/memory.test.ts +140 -0
- package/src/memory/store.ts +174 -0
- package/src/memory/tool.ts +76 -0
- package/src/memory/types.ts +76 -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/kaleidoswap-atomic.test.ts +138 -0
- package/src/recipe/kaleidoswap-atomic.ts +117 -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 +134 -0
- package/src/recipe/swap.ts +74 -0
- package/src/recipe/types.ts +76 -0
- package/src/skills/registry.ts +21 -2
- package/src/skills/skills.test.ts +42 -0
- package/src/tools/cli.test.ts +53 -0
- package/src/tools/cli.ts +98 -0
- package/src/tools/mcp.ts +3 -2
- package/src/wallet/confirm.test.ts +57 -0
- package/src/wallet/confirm.ts +74 -0
- package/src/wallet/contract.test.ts +89 -0
- package/src/wallet/contract.ts +157 -0
- package/skills/kaleido-wallet/SKILL.md +0 -28
package/src/funnel.ts
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Funnel — the tiered agent loop (T0 fast-path → T2 recipe → T1 agentic).
|
|
3
|
+
*
|
|
4
|
+
* This is the mobile-optimized funnel from the roadmap, lifted out of the
|
|
5
|
+
* hosts so every surface (rate chat + voice, desktop provider, agent) runs
|
|
6
|
+
* the SAME routing:
|
|
7
|
+
*
|
|
8
|
+
* request
|
|
9
|
+
* ├─ T0 deterministic fast-path (no LLM) balance / address / price
|
|
10
|
+
* ├─ T2 recipe multi-step (~1 inference) "pay bob 3 EUR"
|
|
11
|
+
* └─ T1 skill-scoped agentic loop everything else
|
|
12
|
+
*
|
|
13
|
+
* Hosts inject the provider, the tool registry, and a `getSettings` closure
|
|
14
|
+
* read fresh each turn — so user-tunable settings (persona, history length,
|
|
15
|
+
* memory/RAG toggles, disabled skills) never require rebuilding the funnel
|
|
16
|
+
* or dropping host state like an embedded RAG index.
|
|
17
|
+
*
|
|
18
|
+
* Safety is unchanged from the Engine: spend tools are confirmation-gated by
|
|
19
|
+
* the contract; with no `onConfirm` the gate fails closed.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { Engine } from './engine.js';
|
|
23
|
+
import type { ToolRegistry } from './tools/registry.js';
|
|
24
|
+
import { FastPath, WALLET_FAST_INTENTS } from './fastpath/fastpath.js';
|
|
25
|
+
import type { FastIntent } from './fastpath/fastpath.js';
|
|
26
|
+
import { RecipeRegistry, runRecipe } from './recipe/runner.js';
|
|
27
|
+
import { paymentsRecipe } from './recipe/payments.js';
|
|
28
|
+
import { receiveRecipe } from './recipe/receive.js';
|
|
29
|
+
import { assetSendRecipe } from './recipe/asset-send.js';
|
|
30
|
+
import type { Recipe } from './recipe/types.js';
|
|
31
|
+
import { SkillRegistry } from './skills/registry.js';
|
|
32
|
+
import type { Skill } from './skills/types.js';
|
|
33
|
+
import type { LLMProvider } from './providers/types.js';
|
|
34
|
+
import type { ConfirmDecision, Message, ToolResult } from './types.js';
|
|
35
|
+
|
|
36
|
+
/** Base system prompt for the wallet assistant. Hosts may override. */
|
|
37
|
+
export const DEFAULT_WALLET_SYSTEM = [
|
|
38
|
+
'You are KaleidoSwap, a concise, privacy-first assistant running inside a',
|
|
39
|
+
'non-custodial Bitcoin, Lightning and RGB wallet.',
|
|
40
|
+
'',
|
|
41
|
+
'CORE RULES (these override every skill instruction):',
|
|
42
|
+
"1. If a tool can answer the user's question, CALL IT. Never describe how a",
|
|
43
|
+
" tool works (\"the pairs are listed using kaleidoswap_get_pairs\") — calling",
|
|
44
|
+
' the tool IS the answer.',
|
|
45
|
+
'2. Never invent a balance, address, amount, price, quote, fee, pair, or any',
|
|
46
|
+
" other value. Every number or identifier in your reply MUST come from a tool",
|
|
47
|
+
' result returned in the CURRENT turn.',
|
|
48
|
+
'3. Never reuse a number, name, or detail from a previous turn unless the user',
|
|
49
|
+
' is explicitly asking about that earlier result. Each new question gets a',
|
|
50
|
+
' fresh tool call.',
|
|
51
|
+
'4. If a tool needs a required argument the user did not give (e.g. an amount',
|
|
52
|
+
" for a quote), ASK for it. Do not invent values. Do not call the tool with",
|
|
53
|
+
' the required field missing.',
|
|
54
|
+
'5. All BTC amounts are in satoshis. Asset codes are case-insensitive but the',
|
|
55
|
+
' canonical forms are BTC, USDT, XAUT — do not silently shorten to USD, XAU.',
|
|
56
|
+
'',
|
|
57
|
+
'Keep replies short and friendly. When a tool returns multiple fields, surface',
|
|
58
|
+
"the ones that matter — never collapse a structured result to a single number",
|
|
59
|
+
'when other fields are non-zero or safety-relevant (e.g. pending balances,',
|
|
60
|
+
'fees, slippage).',
|
|
61
|
+
].join('\n');
|
|
62
|
+
|
|
63
|
+
/** Tools that stay available even when a skill narrows the set. */
|
|
64
|
+
const AMBIENT_MEMORY = ['remember', 'recall'];
|
|
65
|
+
const AMBIENT_RAG = ['search_knowledge'];
|
|
66
|
+
|
|
67
|
+
const DEFAULT_HISTORY = 8;
|
|
68
|
+
|
|
69
|
+
/** Per-user agent settings, read fresh each turn via `getSettings`. */
|
|
70
|
+
export interface FunnelSettings {
|
|
71
|
+
/** Extra instructions appended to the system prompt. */
|
|
72
|
+
persona?: string;
|
|
73
|
+
/** Most recent history messages to keep in the prompt (default 8). */
|
|
74
|
+
historyLength?: number;
|
|
75
|
+
/** Expose the remember/recall tools (default true). */
|
|
76
|
+
memoryEnabled?: boolean;
|
|
77
|
+
/** Expose the search_knowledge tool (default true). */
|
|
78
|
+
ragEnabled?: boolean;
|
|
79
|
+
/** Skill names the user turned off. */
|
|
80
|
+
disabledSkills?: string[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface FunnelCallbacks {
|
|
84
|
+
history?: Message[];
|
|
85
|
+
/** The live requestId of the agentic run (so a stop button can cancel it). */
|
|
86
|
+
onStart?: (requestId: string) => void;
|
|
87
|
+
onToken?: (token: string, turn: number) => void;
|
|
88
|
+
/** A recipe step is executing (deterministic tier). */
|
|
89
|
+
onStep?: (name: string) => void;
|
|
90
|
+
/** The model requested a tool (agentic tier), before it executes. */
|
|
91
|
+
onToolCall?: (
|
|
92
|
+
call: { name: string; arguments: Record<string, unknown> },
|
|
93
|
+
info: { requiresConfirmation: boolean },
|
|
94
|
+
) => void;
|
|
95
|
+
/** A tool returned a result (agentic tier). Errors arrive as `{error}`. */
|
|
96
|
+
onToolResult?: (event: {
|
|
97
|
+
name: string;
|
|
98
|
+
arguments: Record<string, unknown>;
|
|
99
|
+
result: unknown;
|
|
100
|
+
}) => void;
|
|
101
|
+
onConfirm?: (call: { name: string; arguments: Record<string, unknown> }) => Promise<ConfirmDecision>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface FunnelResult {
|
|
105
|
+
text: string;
|
|
106
|
+
tier: 'fast' | 'recipe' | 'agentic';
|
|
107
|
+
/** Fast tier only: the matched intent + raw tool result (e.g. for a balance card). */
|
|
108
|
+
intent?: string;
|
|
109
|
+
data?: unknown;
|
|
110
|
+
/** Agentic tier only: executed tool calls + reasoning turns. */
|
|
111
|
+
toolCalls?: ToolResult[];
|
|
112
|
+
turns?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface FunnelOptions {
|
|
116
|
+
provider: LLMProvider;
|
|
117
|
+
/** ALL tool sources merged — wallet, memory, RAG, merchant, L402, … */
|
|
118
|
+
tools: ToolRegistry;
|
|
119
|
+
/** Skills available to the agentic tier (disabled ones filtered per turn). */
|
|
120
|
+
skills?: Skill[];
|
|
121
|
+
/** Recipes for the T2 tier. Default: asset-send, payments, receive. */
|
|
122
|
+
recipes?: Recipe[];
|
|
123
|
+
/** Fast-path intents for the T0 tier. Default: WALLET_FAST_INTENTS. */
|
|
124
|
+
fastIntents?: FastIntent[];
|
|
125
|
+
/** Base system prompt (persona is appended). Default: DEFAULT_WALLET_SYSTEM. */
|
|
126
|
+
system?: string;
|
|
127
|
+
/** Max reasoning↔tool rounds in the agentic tier. Default 5. */
|
|
128
|
+
maxTurns?: number;
|
|
129
|
+
/** User settings, read fresh each turn. */
|
|
130
|
+
getSettings?: () => FunnelSettings;
|
|
131
|
+
/** Render a fast-path tool result as user-facing text. Default: built-in. */
|
|
132
|
+
renderFast?: (intent: string, result: unknown) => string;
|
|
133
|
+
/** Diagnostics sink (tier routing, tool calls). Default: silent. */
|
|
134
|
+
log?: (message: string) => void;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function defaultRenderFast(intent: string, r: any): string {
|
|
138
|
+
if (intent === 'balance') {
|
|
139
|
+
const sats = Number(r?.total_sats ?? 0);
|
|
140
|
+
const n = r?.layers?.length ?? 0;
|
|
141
|
+
return `You have ${sats.toLocaleString()} sats${n > 1 ? ` across ${n} layers` : ''}.`;
|
|
142
|
+
}
|
|
143
|
+
if (intent === 'address') {
|
|
144
|
+
return r?.address ? `Here's your receive address:\n\n\`${r.address}\`` : 'No address available right now.';
|
|
145
|
+
}
|
|
146
|
+
return `Bitcoin is $${Number(r?.price_usd ?? 0).toLocaleString()}.`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export class Funnel {
|
|
150
|
+
private readonly provider: LLMProvider;
|
|
151
|
+
private readonly registry: ToolRegistry;
|
|
152
|
+
private readonly engine: Engine;
|
|
153
|
+
private readonly fastPath: FastPath;
|
|
154
|
+
private readonly recipes: RecipeRegistry;
|
|
155
|
+
private readonly allSkills: Skill[];
|
|
156
|
+
private readonly system: string;
|
|
157
|
+
private readonly getSettings: () => FunnelSettings;
|
|
158
|
+
private readonly renderFast: (intent: string, result: unknown) => string;
|
|
159
|
+
private readonly log: (message: string) => void;
|
|
160
|
+
|
|
161
|
+
/** Skill registry, rebuilt only when the disabled-skills set changes. */
|
|
162
|
+
private skillsCache: { key: string; reg: SkillRegistry } | null = null;
|
|
163
|
+
|
|
164
|
+
constructor(opts: FunnelOptions) {
|
|
165
|
+
this.provider = opts.provider;
|
|
166
|
+
this.registry = opts.tools;
|
|
167
|
+
this.engine = new Engine({
|
|
168
|
+
provider: opts.provider,
|
|
169
|
+
tools: opts.tools,
|
|
170
|
+
defaultMaxTurns: opts.maxTurns ?? 5,
|
|
171
|
+
});
|
|
172
|
+
this.fastPath = new FastPath(opts.fastIntents ?? WALLET_FAST_INTENTS);
|
|
173
|
+
this.recipes = new RecipeRegistry(opts.recipes ?? [assetSendRecipe, paymentsRecipe, receiveRecipe]);
|
|
174
|
+
this.allSkills = opts.skills ?? [];
|
|
175
|
+
this.system = opts.system ?? DEFAULT_WALLET_SYSTEM;
|
|
176
|
+
this.getSettings = opts.getSettings ?? (() => ({}));
|
|
177
|
+
this.renderFast = opts.renderFast ?? defaultRenderFast;
|
|
178
|
+
this.log = opts.log ?? (() => {});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Skills currently enabled (e.g. for a skills sheet). */
|
|
182
|
+
listSkills(): Skill[] {
|
|
183
|
+
return this.skillsFor(this.getSettings().disabledSkills).list();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private skillsFor(disabled: string[] = []): SkillRegistry {
|
|
187
|
+
const key = [...disabled].sort().join(',');
|
|
188
|
+
if (this.skillsCache?.key !== key) {
|
|
189
|
+
this.skillsCache = {
|
|
190
|
+
key,
|
|
191
|
+
reg: new SkillRegistry(this.allSkills.filter((s) => !disabled.includes(s.name))),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return this.skillsCache.reg;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async runTurn(text: string, cbs: FunnelCallbacks = {}): Promise<FunnelResult> {
|
|
198
|
+
const settings = this.getSettings();
|
|
199
|
+
|
|
200
|
+
// ── T0: deterministic fast-path (no LLM) ──
|
|
201
|
+
// Only fires when the host's registry actually implements the intent's
|
|
202
|
+
// tool — a partial tool surface (e.g. desktop without the core aggregate
|
|
203
|
+
// helpers) falls through to the agentic tier instead of erroring.
|
|
204
|
+
const fast = this.fastPath.select(text);
|
|
205
|
+
if (fast && (await this.registry.getDef(fast.tool))) {
|
|
206
|
+
this.log(`tier=fast-path → ${fast.tool}`);
|
|
207
|
+
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 };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── T2: recipe multi-step — fires only when the recipe is confident given
|
|
212
|
+
// its extracted slots (payments need a recipient; receive always fires),
|
|
213
|
+
// and the registry implements the recipe's final action.
|
|
214
|
+
const recipe = this.recipes.select(text);
|
|
215
|
+
const slots = recipe?.extract?.(text) ?? null;
|
|
216
|
+
const fires =
|
|
217
|
+
!!recipe &&
|
|
218
|
+
!!slots &&
|
|
219
|
+
(recipe.confident ? recipe.confident(slots) : Object.keys(slots).length > 0) &&
|
|
220
|
+
!!(await this.registry.getDef(recipe.final.tool));
|
|
221
|
+
if (recipe && fires) {
|
|
222
|
+
this.log(`tier=recipe:${recipe.name} slots=${JSON.stringify(slots)}`);
|
|
223
|
+
const res = await runRecipe(recipe, text, {
|
|
224
|
+
provider: this.provider,
|
|
225
|
+
tools: this.registry,
|
|
226
|
+
onConfirm: cbs.onConfirm,
|
|
227
|
+
onStep: (name) => {
|
|
228
|
+
this.log(`step ${name}`);
|
|
229
|
+
cbs.onStep?.(name);
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
return { text: res.text, tier: 'recipe' };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ── T1: skill-scoped agentic loop ──
|
|
236
|
+
const skills = this.skillsFor(settings.disabledSkills);
|
|
237
|
+
const skill = skills.select(text);
|
|
238
|
+
const base = settings.persona ? `${this.system}\n\n## Your persona\n${settings.persona}` : this.system;
|
|
239
|
+
const { system, allowedTools } = skills.compose(base, skill);
|
|
240
|
+
|
|
241
|
+
// Ambient tools stay available even when a skill narrows the set — gated
|
|
242
|
+
// by the user's memory/knowledge toggles (default on).
|
|
243
|
+
const memoryOn = settings.memoryEnabled !== false;
|
|
244
|
+
const ragOn = settings.ragEnabled !== false;
|
|
245
|
+
const ambient = [...(memoryOn ? AMBIENT_MEMORY : []), ...(ragOn ? AMBIENT_RAG : [])];
|
|
246
|
+
const disabledAmbient = [...(memoryOn ? [] : AMBIENT_MEMORY), ...(ragOn ? [] : AMBIENT_RAG)];
|
|
247
|
+
let scoped: string[] | undefined;
|
|
248
|
+
if (allowedTools) {
|
|
249
|
+
scoped = [...new Set([...allowedTools, ...ambient])];
|
|
250
|
+
} else if (disabledAmbient.length) {
|
|
251
|
+
// No skill matched but a toggle is off: expose everything except the
|
|
252
|
+
// disabled ambient tools (the sources stay mounted — no rebuild).
|
|
253
|
+
const all = (await this.registry.listTools()).map((t) => t.name);
|
|
254
|
+
scoped = all.filter((n) => !disabledAmbient.includes(n));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Trim history so the prompt (system + skill + tools + history) stays
|
|
258
|
+
// within the small on-device model's context window.
|
|
259
|
+
const keep = settings.historyLength ?? DEFAULT_HISTORY;
|
|
260
|
+
const history = (cbs.history ?? []).slice(-keep);
|
|
261
|
+
const messages: Message[] = [
|
|
262
|
+
{ role: 'system', content: system },
|
|
263
|
+
...history,
|
|
264
|
+
{ role: 'user', content: text },
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
this.log(`tier=agentic skill=${skill?.name ?? 'none'} tools=[${(scoped ?? ['all']).join(',')}]`);
|
|
268
|
+
const res = await this.engine.runAgentic(messages, {
|
|
269
|
+
allowedTools: scoped,
|
|
270
|
+
onStart: (requestId) => cbs.onStart?.(requestId),
|
|
271
|
+
onToken: cbs.onToken,
|
|
272
|
+
onToolCall: (call) => {
|
|
273
|
+
this.log(`tool ${call.name} ${JSON.stringify(call.arguments)}`);
|
|
274
|
+
// getDef is async; fire-and-forget so the loop is never blocked on UI.
|
|
275
|
+
void this.registry
|
|
276
|
+
.getDef(call.name)
|
|
277
|
+
.then((def) => cbs.onToolCall?.(call, { requiresConfirmation: !!def?.requiresConfirmation }))
|
|
278
|
+
.catch(() => cbs.onToolCall?.(call, { requiresConfirmation: false }));
|
|
279
|
+
},
|
|
280
|
+
onToolResult: cbs.onToolResult,
|
|
281
|
+
onConfirm: cbs.onConfirm,
|
|
282
|
+
});
|
|
283
|
+
return { text: res.text ?? '', tier: 'agentic', toolCalls: res.toolCalls, turns: res.turns };
|
|
284
|
+
}
|
|
285
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -29,10 +29,138 @@ export {
|
|
|
29
29
|
bolt11AmountSats,
|
|
30
30
|
} from './tools/l402.js';
|
|
31
31
|
export type { L402Options, L402PayResult } from './tools/l402.js';
|
|
32
|
+
export { createCliToolSource, isAllowed } from './tools/cli.js';
|
|
33
|
+
export type { CliToolOptions, CommandRunner, CommandResult } from './tools/cli.js';
|
|
34
|
+
|
|
35
|
+
// ── Multi-L2 wallet tool contract (single source of truth) ─────────────────
|
|
36
|
+
export {
|
|
37
|
+
WALLET_TOOLS,
|
|
38
|
+
WALLET_LAYERS,
|
|
39
|
+
SPEND_TOOLS,
|
|
40
|
+
isSpendTool,
|
|
41
|
+
getWalletTool,
|
|
42
|
+
walletTools,
|
|
43
|
+
toToolDefs,
|
|
44
|
+
bindWalletTools,
|
|
45
|
+
} from './wallet/contract.js';
|
|
46
|
+
export type {
|
|
47
|
+
WalletLayer,
|
|
48
|
+
WalletToolDef,
|
|
49
|
+
WalletHandler,
|
|
50
|
+
BindWalletOptions,
|
|
51
|
+
} from './wallet/contract.js';
|
|
52
|
+
export { confirmReadback } from './wallet/confirm.js';
|
|
53
|
+
|
|
54
|
+
// ── KaleidoSwap maker tool contract (single source of truth) ────────────────
|
|
55
|
+
export {
|
|
56
|
+
KALEIDOSWAP_TOOLS,
|
|
57
|
+
KALEIDOSWAP_SPEND_TOOLS,
|
|
58
|
+
isKaleidoswapSpendTool,
|
|
59
|
+
getKaleidoswapTool,
|
|
60
|
+
kaleidoswapTools,
|
|
61
|
+
bindKaleidoswapTools,
|
|
62
|
+
} from './kaleidoswap/contract.js';
|
|
63
|
+
export type {
|
|
64
|
+
KaleidoswapGroup,
|
|
65
|
+
KaleidoswapToolDef,
|
|
66
|
+
KaleidoswapHandler,
|
|
67
|
+
BindKaleidoswapOptions,
|
|
68
|
+
} from './kaleidoswap/contract.js';
|
|
69
|
+
|
|
70
|
+
// ── LSPS1 (Lightning Service Provider channel orders) ───────────────────────
|
|
71
|
+
export {
|
|
72
|
+
LSPS1_TOOLS,
|
|
73
|
+
LSPS1_SPEND_TOOLS,
|
|
74
|
+
isLsps1SpendTool,
|
|
75
|
+
getLsps1Tool,
|
|
76
|
+
bindLsps1Tools,
|
|
77
|
+
} from './lsps1/contract.js';
|
|
78
|
+
export type {
|
|
79
|
+
Lsps1ToolDef,
|
|
80
|
+
Lsps1Handler,
|
|
81
|
+
BindLsps1Options,
|
|
82
|
+
} from './lsps1/contract.js';
|
|
83
|
+
|
|
84
|
+
// ── KaleidoSwap atomic-swap recipe (opt-in — register via Funnel.recipes) ──
|
|
85
|
+
export { kaleidoswapAtomicRecipe } from './recipe/kaleidoswap-atomic.js';
|
|
86
|
+
|
|
87
|
+
// ── Recipes (mobile multi-step: "recipes, not planning") ───────────────────
|
|
88
|
+
export { runRecipe, extractSlots, RecipeRegistry } from './recipe/runner.js';
|
|
89
|
+
export type { RunRecipeOptions } from './recipe/runner.js';
|
|
90
|
+
export { paymentsRecipe, extractPayment } from './recipe/payments.js';
|
|
91
|
+
export { swapRecipe, extractSwap } from './recipe/swap.js';
|
|
92
|
+
export { receiveRecipe, extractReceive } from './recipe/receive.js';
|
|
93
|
+
export { assetSendRecipe, extractAssetSend } from './recipe/asset-send.js';
|
|
94
|
+
export type { Recipe, RecipeStep, RecipeSlot, RecipeContext, RecipeResult, RecipeStatus } from './recipe/types.js';
|
|
95
|
+
|
|
96
|
+
// ── Tier-0 deterministic fast-path (no LLM) ────────────────────────────────
|
|
97
|
+
export { FastPath, WALLET_FAST_INTENTS } from './fastpath/fastpath.js';
|
|
98
|
+
export type { FastIntent, FastHit } from './fastpath/fastpath.js';
|
|
99
|
+
|
|
100
|
+
// ── Memory (soul + recall) ───────────────────────────────────────────────
|
|
101
|
+
export { InMemoryMemoryStore } from './memory/store.js';
|
|
102
|
+
export type { MemoryStoreOptions } from './memory/store.js';
|
|
103
|
+
export { createMemoryToolSource } from './memory/tool.js';
|
|
104
|
+
export type {
|
|
105
|
+
AgentProfile,
|
|
106
|
+
MemoryConsolidation,
|
|
107
|
+
MemoryItem,
|
|
108
|
+
MemoryKind,
|
|
109
|
+
MemoryQuery,
|
|
110
|
+
MemoryStore,
|
|
111
|
+
MemoryIO,
|
|
112
|
+
NewMemory,
|
|
113
|
+
} from './memory/types.js';
|
|
114
|
+
|
|
115
|
+
// ── RAG ──────────────────────────────────────────────────────────────────
|
|
116
|
+
export { Retriever, chunkText } from './rag/retriever.js';
|
|
117
|
+
export type { RetrieverOptions } from './rag/retriever.js';
|
|
118
|
+
export { InMemoryVectorStore, cosineSimilarity } from './rag/vector-store.js';
|
|
119
|
+
export { createRagToolSource } from './rag/tool.js';
|
|
120
|
+
export type { RagToolOptions } from './rag/tool.js';
|
|
121
|
+
export type {
|
|
122
|
+
EmbeddingProvider,
|
|
123
|
+
Chunk,
|
|
124
|
+
RetrievedChunk,
|
|
125
|
+
RagDocument,
|
|
126
|
+
VectorStore,
|
|
127
|
+
VectorStoreIO,
|
|
128
|
+
} from './rag/types.js';
|
|
129
|
+
|
|
130
|
+
// ── Context assembly + hardware budget ─────────────────────────────────────
|
|
131
|
+
export { ContextBuilder } from './context/builder.js';
|
|
132
|
+
export type { ContextBuilderOptions, BuildInput } from './context/builder.js';
|
|
133
|
+
export {
|
|
134
|
+
estimateTokens,
|
|
135
|
+
clampToTokens,
|
|
136
|
+
contextBudgetTokens,
|
|
137
|
+
} from './context/budget.js';
|
|
138
|
+
export type { BudgetReserves } from './context/budget.js';
|
|
139
|
+
export { capabilityProfile } from './capabilities.js';
|
|
140
|
+
export type { CapabilityInput, MindCapabilities } from './capabilities.js';
|
|
141
|
+
|
|
142
|
+
// ── Knowledge packs + corpus adapters (for RAG) ────────────────────────────
|
|
143
|
+
export { BITCOIN_COPILOT_DOCS } from './knowledge/bitcoin-copilot.js';
|
|
144
|
+
export { walletHistoryToDocuments, contactsToDocuments } from './knowledge/wallet.js';
|
|
145
|
+
export type { WalletTx, Contact } from './knowledge/wallet.js';
|
|
146
|
+
export { merchantsToDocuments } from './knowledge/merchants.js';
|
|
147
|
+
export type { Merchant } from './knowledge/merchants.js';
|
|
148
|
+
export { createBtcMapToolSource, BTC_MAP_SAMPLE } from './knowledge/btc-map.js';
|
|
149
|
+
export type {
|
|
150
|
+
BtcMapToolOptions,
|
|
151
|
+
BtcMapMerchant,
|
|
152
|
+
BtcMapFetch,
|
|
153
|
+
LocationProvider,
|
|
154
|
+
LatLng,
|
|
155
|
+
} from './knowledge/btc-map.js';
|
|
32
156
|
|
|
33
157
|
export { Engine } from './engine.js';
|
|
34
158
|
export type { EngineOptions, AgenticOptions, AgenticResult } from './engine.js';
|
|
35
159
|
|
|
160
|
+
// ── Funnel (T0 fast-path → T2 recipe → T1 agentic — the tiered agent) ───────
|
|
161
|
+
export { Funnel, DEFAULT_WALLET_SYSTEM } from './funnel.js';
|
|
162
|
+
export type { FunnelOptions, FunnelSettings, FunnelCallbacks, FunnelResult } from './funnel.js';
|
|
163
|
+
|
|
36
164
|
export {
|
|
37
165
|
SkillRegistry,
|
|
38
166
|
parseSkill,
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
KALEIDOSWAP_TOOLS,
|
|
4
|
+
KALEIDOSWAP_SPEND_TOOLS,
|
|
5
|
+
isKaleidoswapSpendTool,
|
|
6
|
+
getKaleidoswapTool,
|
|
7
|
+
kaleidoswapTools,
|
|
8
|
+
bindKaleidoswapTools,
|
|
9
|
+
type KaleidoswapHandler,
|
|
10
|
+
} from './contract.js';
|
|
11
|
+
|
|
12
|
+
describe('KALEIDOSWAP_TOOLS — shape invariants', () => {
|
|
13
|
+
it('exposes the expected tool names', () => {
|
|
14
|
+
const names = KALEIDOSWAP_TOOLS.map((t) => t.name);
|
|
15
|
+
expect(names).toEqual([
|
|
16
|
+
'kaleidoswap_get_assets',
|
|
17
|
+
'kaleidoswap_get_pairs',
|
|
18
|
+
'kaleidoswap_get_quote',
|
|
19
|
+
'kaleidoswap_get_nodeinfo',
|
|
20
|
+
'kaleidoswap_place_order',
|
|
21
|
+
'kaleidoswap_get_order_status',
|
|
22
|
+
'kaleidoswap_get_order_history',
|
|
23
|
+
'kaleidoswap_atomic_init',
|
|
24
|
+
'kaleidoswap_atomic_execute',
|
|
25
|
+
'kaleidoswap_atomic_status',
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('every tool has a group and a parameters object', () => {
|
|
30
|
+
for (const t of KALEIDOSWAP_TOOLS) {
|
|
31
|
+
expect(['market', 'orders', 'atomic']).toContain(t.group);
|
|
32
|
+
expect(t.parameters).toBeDefined();
|
|
33
|
+
expect((t.parameters as any).type).toBe('object');
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('marks every spend tool as requiresConfirmation', () => {
|
|
38
|
+
for (const t of KALEIDOSWAP_TOOLS) {
|
|
39
|
+
expect(!!t.spend).toBe(!!t.requiresConfirmation);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('lists every spend tool exactly once in KALEIDOSWAP_SPEND_TOOLS', () => {
|
|
44
|
+
const expected = KALEIDOSWAP_TOOLS.filter((t) => t.spend).map((t) => t.name).sort();
|
|
45
|
+
expect([...KALEIDOSWAP_SPEND_TOOLS].sort()).toEqual(expected);
|
|
46
|
+
// Sanity: place_order, atomic_init, atomic_execute are spend; the rest aren't.
|
|
47
|
+
expect(expected).toEqual([
|
|
48
|
+
'kaleidoswap_atomic_execute',
|
|
49
|
+
'kaleidoswap_atomic_init',
|
|
50
|
+
'kaleidoswap_place_order',
|
|
51
|
+
]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('isKaleidoswapSpendTool agrees with the set', () => {
|
|
55
|
+
expect(isKaleidoswapSpendTool('kaleidoswap_place_order')).toBe(true);
|
|
56
|
+
expect(isKaleidoswapSpendTool('kaleidoswap_get_pairs')).toBe(false);
|
|
57
|
+
expect(isKaleidoswapSpendTool('not_a_tool')).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('getKaleidoswapTool returns by name', () => {
|
|
61
|
+
expect(getKaleidoswapTool('kaleidoswap_get_quote')?.group).toBe('market');
|
|
62
|
+
expect(getKaleidoswapTool('nope')).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('kaleidoswapTools(groups)', () => {
|
|
67
|
+
it('returns all tools when no group filter', () => {
|
|
68
|
+
expect(kaleidoswapTools().length).toBe(KALEIDOSWAP_TOOLS.length);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('filters by group', () => {
|
|
72
|
+
const market = kaleidoswapTools({ groups: ['market'] });
|
|
73
|
+
expect(market.every((t) => t.group === 'market')).toBe(true);
|
|
74
|
+
expect(market.map((t) => t.name)).toContain('kaleidoswap_get_quote');
|
|
75
|
+
expect(market.map((t) => t.name)).not.toContain('kaleidoswap_place_order');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('combines multiple groups', () => {
|
|
79
|
+
const readPlusOrders = kaleidoswapTools({ groups: ['market', 'orders'] });
|
|
80
|
+
expect(readPlusOrders.some((t) => t.name === 'kaleidoswap_atomic_init')).toBe(false);
|
|
81
|
+
expect(readPlusOrders.some((t) => t.name === 'kaleidoswap_place_order')).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('bindKaleidoswapTools', () => {
|
|
86
|
+
// Handlers that just echo their args so we can verify wiring.
|
|
87
|
+
const echoHandlers = (): Record<string, KaleidoswapHandler> => ({
|
|
88
|
+
kaleidoswap_get_assets: async () => ({ ok: true, tool: 'get_assets' }),
|
|
89
|
+
kaleidoswap_get_pairs: async () => ({ ok: true, tool: 'get_pairs' }),
|
|
90
|
+
kaleidoswap_get_quote: async (a) => ({ ok: true, tool: 'get_quote', args: a }),
|
|
91
|
+
kaleidoswap_get_nodeinfo: async () => ({ ok: true, tool: 'get_nodeinfo' }),
|
|
92
|
+
kaleidoswap_place_order: async (a) => ({ ok: true, tool: 'place_order', args: a }),
|
|
93
|
+
kaleidoswap_get_order_status: async (a) => ({ ok: true, tool: 'get_order_status', args: a }),
|
|
94
|
+
kaleidoswap_get_order_history: async (a) => ({ ok: true, tool: 'get_order_history', args: a }),
|
|
95
|
+
kaleidoswap_atomic_init: async (a) => ({ ok: true, tool: 'atomic_init', args: a }),
|
|
96
|
+
kaleidoswap_atomic_execute: async (a) => ({ ok: true, tool: 'atomic_execute', args: a }),
|
|
97
|
+
kaleidoswap_atomic_status: async (a) => ({ ok: true, tool: 'atomic_status', args: a }),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('binds every tool when all handlers are present', () => {
|
|
101
|
+
const src = bindKaleidoswapTools(echoHandlers());
|
|
102
|
+
const tools = src.listTools();
|
|
103
|
+
expect(tools.length).toBe(KALEIDOSWAP_TOOLS.length);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('preserves descriptions and the spend gate (requiresConfirmation)', () => {
|
|
107
|
+
const src = bindKaleidoswapTools(echoHandlers());
|
|
108
|
+
const place = src.listTools().find((t) => t.name === 'kaleidoswap_place_order');
|
|
109
|
+
const pairs = src.listTools().find((t) => t.name === 'kaleidoswap_get_pairs');
|
|
110
|
+
expect(place?.requiresConfirmation).toBe(true);
|
|
111
|
+
expect(pairs?.requiresConfirmation).toBeFalsy();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('dispatches execute() to the right handler with args', async () => {
|
|
115
|
+
const src = bindKaleidoswapTools(echoHandlers());
|
|
116
|
+
const r = await src.execute('kaleidoswap_get_quote', { from_asset: 'BTC', to_asset: 'USDT', amount: 100_000 });
|
|
117
|
+
expect(r).toMatchObject({ ok: true, tool: 'get_quote', args: { from_asset: 'BTC', amount: 100_000 } });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('throws when a handler is missing and allowMissing is false', () => {
|
|
121
|
+
const handlers = { kaleidoswap_get_pairs: echoHandlers().kaleidoswap_get_pairs };
|
|
122
|
+
expect(() => bindKaleidoswapTools(handlers)).toThrow(/no handler/);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('skips missing handlers when allowMissing is true', () => {
|
|
126
|
+
const handlers: Record<string, KaleidoswapHandler> = {
|
|
127
|
+
kaleidoswap_get_pairs: async () => ({ ok: true }),
|
|
128
|
+
kaleidoswap_get_quote: async () => ({ ok: true }),
|
|
129
|
+
};
|
|
130
|
+
const src = bindKaleidoswapTools(handlers, { allowMissing: true });
|
|
131
|
+
const names = src.listTools().map((t) => t.name);
|
|
132
|
+
expect(names).toEqual(['kaleidoswap_get_pairs', 'kaleidoswap_get_quote']);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('respects the groups filter when binding', () => {
|
|
136
|
+
const src = bindKaleidoswapTools(echoHandlers(), { groups: ['market'] });
|
|
137
|
+
const names = src.listTools().map((t) => t.name);
|
|
138
|
+
expect(names).toContain('kaleidoswap_get_quote');
|
|
139
|
+
expect(names).not.toContain('kaleidoswap_place_order');
|
|
140
|
+
expect(names).not.toContain('kaleidoswap_atomic_init');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('uses opts.id for the ToolSource id', () => {
|
|
144
|
+
const src = bindKaleidoswapTools(echoHandlers(), { id: 'maker-prod' });
|
|
145
|
+
expect(src.id).toBe('maker-prod');
|
|
146
|
+
});
|
|
147
|
+
});
|