kalshi-trading-bot-cli 2.1.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/LICENSE +21 -0
- package/README.md +360 -0
- package/assets/kalshi-flow-light.png +0 -0
- package/assets/screenshot.png +0 -0
- package/env.example +43 -0
- package/kalshi-flow-light.png +0 -0
- package/package.json +66 -0
- package/src/agent/agent.ts +249 -0
- package/src/agent/channels.ts +53 -0
- package/src/agent/index.ts +29 -0
- package/src/agent/prompts.ts +171 -0
- package/src/agent/run-context.ts +23 -0
- package/src/agent/scratchpad.ts +465 -0
- package/src/agent/token-counter.ts +33 -0
- package/src/agent/tool-executor.ts +166 -0
- package/src/agent/types.ts +221 -0
- package/src/audit/index.ts +25 -0
- package/src/audit/reader.ts +43 -0
- package/src/audit/trail.ts +29 -0
- package/src/audit/types.ts +133 -0
- package/src/backtest/discovery.ts +170 -0
- package/src/backtest/fetcher.ts +247 -0
- package/src/backtest/metrics.ts +165 -0
- package/src/backtest/renderer.ts +196 -0
- package/src/backtest/types.ts +45 -0
- package/src/cli.ts +943 -0
- package/src/commands/alerts.ts +48 -0
- package/src/commands/analyze.ts +662 -0
- package/src/commands/backtest.ts +276 -0
- package/src/commands/clear-cache.ts +24 -0
- package/src/commands/config.ts +107 -0
- package/src/commands/dispatch.ts +473 -0
- package/src/commands/edge.ts +62 -0
- package/src/commands/formatters.ts +339 -0
- package/src/commands/help.ts +263 -0
- package/src/commands/helpers.ts +48 -0
- package/src/commands/index.ts +287 -0
- package/src/commands/json.ts +43 -0
- package/src/commands/parse-args.ts +229 -0
- package/src/commands/portfolio.ts +236 -0
- package/src/commands/review.ts +176 -0
- package/src/commands/scan-formatters.ts +98 -0
- package/src/commands/scan.ts +38 -0
- package/src/commands/search-edge.ts +139 -0
- package/src/commands/status.ts +70 -0
- package/src/commands/themes.ts +117 -0
- package/src/commands/watch.ts +295 -0
- package/src/components/answer-box.ts +57 -0
- package/src/components/approval-prompt.ts +34 -0
- package/src/components/browse-list.ts +134 -0
- package/src/components/chat-log.ts +291 -0
- package/src/components/custom-editor.ts +18 -0
- package/src/components/debug-panel.ts +52 -0
- package/src/components/index.ts +17 -0
- package/src/components/intro.ts +92 -0
- package/src/components/select-list.ts +155 -0
- package/src/components/tool-event.ts +127 -0
- package/src/components/user-query.ts +18 -0
- package/src/components/working-indicator.ts +87 -0
- package/src/controllers/agent-runner.ts +283 -0
- package/src/controllers/browse.ts +1013 -0
- package/src/controllers/index.ts +7 -0
- package/src/controllers/input-history.ts +76 -0
- package/src/controllers/model-selection.ts +244 -0
- package/src/db/alerts.ts +77 -0
- package/src/db/edge.ts +105 -0
- package/src/db/event-index.ts +323 -0
- package/src/db/events.ts +41 -0
- package/src/db/index.ts +60 -0
- package/src/db/octagon-cache.ts +118 -0
- package/src/db/positions.ts +71 -0
- package/src/db/risk.ts +51 -0
- package/src/db/schema.ts +227 -0
- package/src/db/themes.ts +34 -0
- package/src/db/trades.ts +50 -0
- package/src/eval/brier.ts +90 -0
- package/src/eval/index.ts +4 -0
- package/src/eval/performance.ts +87 -0
- package/src/gateway/access-control.ts +253 -0
- package/src/gateway/agent-runner.ts +75 -0
- package/src/gateway/alerts/formatter.ts +90 -0
- package/src/gateway/alerts/index.ts +4 -0
- package/src/gateway/alerts/router.ts +32 -0
- package/src/gateway/alerts/terminal.ts +16 -0
- package/src/gateway/alerts/types.ts +13 -0
- package/src/gateway/channels/index.ts +9 -0
- package/src/gateway/channels/manager.ts +153 -0
- package/src/gateway/channels/types.ts +48 -0
- package/src/gateway/channels/whatsapp/README.md +234 -0
- package/src/gateway/channels/whatsapp/auth-store.ts +140 -0
- package/src/gateway/channels/whatsapp/dedupe.ts +60 -0
- package/src/gateway/channels/whatsapp/error.ts +122 -0
- package/src/gateway/channels/whatsapp/inbound.ts +326 -0
- package/src/gateway/channels/whatsapp/index.ts +5 -0
- package/src/gateway/channels/whatsapp/lid.ts +56 -0
- package/src/gateway/channels/whatsapp/logger.ts +25 -0
- package/src/gateway/channels/whatsapp/login.ts +94 -0
- package/src/gateway/channels/whatsapp/outbound.ts +119 -0
- package/src/gateway/channels/whatsapp/plugin.ts +54 -0
- package/src/gateway/channels/whatsapp/reconnect.ts +40 -0
- package/src/gateway/channels/whatsapp/runtime.ts +122 -0
- package/src/gateway/channels/whatsapp/session.ts +89 -0
- package/src/gateway/channels/whatsapp/types.ts +32 -0
- package/src/gateway/commands/handler.ts +64 -0
- package/src/gateway/commands/index.ts +7 -0
- package/src/gateway/commands/parser.ts +29 -0
- package/src/gateway/commands/wa-formatters.ts +92 -0
- package/src/gateway/config.ts +244 -0
- package/src/gateway/extension-points.ts +17 -0
- package/src/gateway/gateway.ts +301 -0
- package/src/gateway/group/history-buffer.ts +75 -0
- package/src/gateway/group/index.ts +8 -0
- package/src/gateway/group/member-tracker.ts +60 -0
- package/src/gateway/group/mention-detection.ts +42 -0
- package/src/gateway/heartbeat/index.ts +8 -0
- package/src/gateway/heartbeat/prompt.ts +73 -0
- package/src/gateway/heartbeat/runner.ts +200 -0
- package/src/gateway/heartbeat/suppression.ts +74 -0
- package/src/gateway/index.ts +138 -0
- package/src/gateway/routing/resolve-route.ts +119 -0
- package/src/gateway/sessions/store.ts +65 -0
- package/src/gateway/types.ts +11 -0
- package/src/gateway/utils.ts +82 -0
- package/src/index.tsx +30 -0
- package/src/model/llm.ts +247 -0
- package/src/providers.ts +94 -0
- package/src/risk/circuit-breaker.ts +113 -0
- package/src/risk/correlation.ts +40 -0
- package/src/risk/gate.ts +125 -0
- package/src/risk/index.ts +10 -0
- package/src/risk/kelly.ts +230 -0
- package/src/scan/alerter.ts +64 -0
- package/src/scan/edge-computer.ts +164 -0
- package/src/scan/invoker.ts +199 -0
- package/src/scan/loop.ts +184 -0
- package/src/scan/octagon-client.ts +627 -0
- package/src/scan/octagon-events-api.ts +105 -0
- package/src/scan/octagon-prefetch.ts +172 -0
- package/src/scan/theme-resolver.ts +179 -0
- package/src/scan/types.ts +62 -0
- package/src/scan/watchdog.ts +126 -0
- package/src/setup/wizard.ts +659 -0
- package/src/theme.ts +67 -0
- package/src/tools/fetch/cache.ts +95 -0
- package/src/tools/fetch/external-content.ts +200 -0
- package/src/tools/fetch/index.ts +1 -0
- package/src/tools/fetch/web-fetch-utils.ts +122 -0
- package/src/tools/fetch/web-fetch.ts +419 -0
- package/src/tools/index.ts +10 -0
- package/src/tools/kalshi/api.ts +251 -0
- package/src/tools/kalshi/dlq.ts +35 -0
- package/src/tools/kalshi/events.ts +84 -0
- package/src/tools/kalshi/exchange.ts +24 -0
- package/src/tools/kalshi/historical.ts +89 -0
- package/src/tools/kalshi/index.ts +11 -0
- package/src/tools/kalshi/kalshi-search.ts +437 -0
- package/src/tools/kalshi/kalshi-trade.ts +102 -0
- package/src/tools/kalshi/markets.ts +76 -0
- package/src/tools/kalshi/portfolio.ts +100 -0
- package/src/tools/kalshi/search-index.ts +198 -0
- package/src/tools/kalshi/series.ts +16 -0
- package/src/tools/kalshi/trading.ts +115 -0
- package/src/tools/kalshi/types.ts +199 -0
- package/src/tools/registry.ts +160 -0
- package/src/tools/search/index.ts +25 -0
- package/src/tools/search/tavily.ts +35 -0
- package/src/tools/types.ts +53 -0
- package/src/tools/v2/edge-query.ts +135 -0
- package/src/tools/v2/octagon-report.ts +112 -0
- package/src/tools/v2/portfolio-query.ts +79 -0
- package/src/tools/v2/portfolio-review.ts +59 -0
- package/src/tools/v2/risk-status.ts +94 -0
- package/src/tools/v2/scan.ts +78 -0
- package/src/types/qrcode-terminal.d.ts +7 -0
- package/src/types/whiskeysockets-baileys.d.ts +41 -0
- package/src/types.ts +22 -0
- package/src/utils/ai-message.ts +26 -0
- package/src/utils/bot-config.ts +219 -0
- package/src/utils/cache.ts +195 -0
- package/src/utils/config.ts +113 -0
- package/src/utils/env.ts +111 -0
- package/src/utils/errors.ts +313 -0
- package/src/utils/history-context.ts +32 -0
- package/src/utils/in-memory-chat-history.ts +268 -0
- package/src/utils/index.ts +28 -0
- package/src/utils/input-key-handlers.ts +64 -0
- package/src/utils/logger.ts +67 -0
- package/src/utils/long-term-chat-history.ts +138 -0
- package/src/utils/markdown-table.ts +227 -0
- package/src/utils/model.ts +70 -0
- package/src/utils/ollama.ts +37 -0
- package/src/utils/paths.ts +12 -0
- package/src/utils/progress-channel.ts +84 -0
- package/src/utils/telemetry.ts +103 -0
- package/src/utils/text-navigation.ts +81 -0
- package/src/utils/thinking-verbs.ts +18 -0
- package/src/utils/tokens.ts +36 -0
- package/src/utils/tool-description.ts +61 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { AIMessage } from '@langchain/core/messages';
|
|
2
|
+
import { StructuredToolInterface } from '@langchain/core/tools';
|
|
3
|
+
import { callLlm } from '../model/llm.js';
|
|
4
|
+
import { getTools } from '../tools/registry.js';
|
|
5
|
+
import { buildSystemPrompt, buildIterationPrompt } from './prompts.js';
|
|
6
|
+
import { extractTextContent, hasToolCalls } from '../utils/ai-message.js';
|
|
7
|
+
import { InMemoryChatHistory } from '../utils/in-memory-chat-history.js';
|
|
8
|
+
import { buildHistoryContext } from '../utils/history-context.js';
|
|
9
|
+
import { estimateTokens, CONTEXT_THRESHOLD, KEEP_TOOL_USES } from '../utils/tokens.js';
|
|
10
|
+
import { formatUserFacingError, isContextOverflowError } from '../utils/errors.js';
|
|
11
|
+
import type { AgentConfig, AgentEvent, ContextClearedEvent, TokenUsage } from '../agent/types.js';
|
|
12
|
+
import { createRunContext, type RunContext } from './run-context.js';
|
|
13
|
+
import { AgentToolExecutor } from './tool-executor.js';
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
const DEFAULT_MODEL = 'gpt-5.4';
|
|
17
|
+
const DEFAULT_MAX_ITERATIONS = 10;
|
|
18
|
+
const MAX_OVERFLOW_RETRIES = 2;
|
|
19
|
+
const OVERFLOW_KEEP_TOOL_USES = 3;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The core agent class that handles the agent loop and tool execution.
|
|
23
|
+
*/
|
|
24
|
+
export class Agent {
|
|
25
|
+
private readonly model: string;
|
|
26
|
+
private readonly maxIterations: number;
|
|
27
|
+
private readonly tools: StructuredToolInterface[];
|
|
28
|
+
private readonly toolMap: Map<string, StructuredToolInterface>;
|
|
29
|
+
private readonly toolExecutor: AgentToolExecutor;
|
|
30
|
+
private readonly systemPrompt: string;
|
|
31
|
+
private readonly signal?: AbortSignal;
|
|
32
|
+
|
|
33
|
+
private constructor(
|
|
34
|
+
config: AgentConfig,
|
|
35
|
+
tools: StructuredToolInterface[],
|
|
36
|
+
systemPrompt: string,
|
|
37
|
+
) {
|
|
38
|
+
this.model = config.model ?? DEFAULT_MODEL;
|
|
39
|
+
this.maxIterations = config.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
40
|
+
this.tools = tools;
|
|
41
|
+
this.toolMap = new Map(tools.map(t => [t.name, t]));
|
|
42
|
+
this.toolExecutor = new AgentToolExecutor(this.toolMap, config.signal, config.requestToolApproval, config.sessionApprovedTools);
|
|
43
|
+
this.systemPrompt = systemPrompt;
|
|
44
|
+
this.signal = config.signal;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a new Agent instance with tools.
|
|
49
|
+
*/
|
|
50
|
+
static async create(config: AgentConfig = {}): Promise<Agent> {
|
|
51
|
+
const model = config.model ?? DEFAULT_MODEL;
|
|
52
|
+
const tools = getTools(model);
|
|
53
|
+
const systemPrompt = buildSystemPrompt(model, config.channel);
|
|
54
|
+
return new Agent(config, tools, systemPrompt);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Run the agent and yield events for real-time UI updates.
|
|
59
|
+
* Anthropic-style context management: full tool results during iteration,
|
|
60
|
+
* with threshold-based clearing of oldest results when context exceeds limit.
|
|
61
|
+
*/
|
|
62
|
+
async *run(query: string, inMemoryHistory?: InMemoryChatHistory): AsyncGenerator<AgentEvent> {
|
|
63
|
+
const startTime = Date.now();
|
|
64
|
+
|
|
65
|
+
if (this.tools.length === 0) {
|
|
66
|
+
yield { type: 'done', answer: 'No tools available. Please check your API key configuration.', toolCalls: [], iterations: 0, totalTime: Date.now() - startTime };
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const ctx = createRunContext(query);
|
|
71
|
+
|
|
72
|
+
// Build initial prompt with conversation history context
|
|
73
|
+
let currentPrompt = this.buildInitialPrompt(query, inMemoryHistory);
|
|
74
|
+
|
|
75
|
+
// Main agent loop
|
|
76
|
+
let overflowRetries = 0;
|
|
77
|
+
while (ctx.iteration < this.maxIterations) {
|
|
78
|
+
ctx.iteration++;
|
|
79
|
+
|
|
80
|
+
let response: AIMessage | string;
|
|
81
|
+
let usage: TokenUsage | undefined;
|
|
82
|
+
|
|
83
|
+
while (true) {
|
|
84
|
+
try {
|
|
85
|
+
const result = await this.callModel(currentPrompt);
|
|
86
|
+
response = result.response;
|
|
87
|
+
usage = result.usage;
|
|
88
|
+
overflowRetries = 0;
|
|
89
|
+
break;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
92
|
+
|
|
93
|
+
if (isContextOverflowError(errorMessage) && overflowRetries < MAX_OVERFLOW_RETRIES) {
|
|
94
|
+
overflowRetries++;
|
|
95
|
+
const clearedCount = ctx.scratchpad.clearOldestToolResults(OVERFLOW_KEEP_TOOL_USES);
|
|
96
|
+
|
|
97
|
+
if (clearedCount > 0) {
|
|
98
|
+
yield { type: 'context_cleared', clearedCount, keptCount: OVERFLOW_KEEP_TOOL_USES };
|
|
99
|
+
currentPrompt = buildIterationPrompt(
|
|
100
|
+
query,
|
|
101
|
+
ctx.scratchpad.getToolResults(),
|
|
102
|
+
ctx.scratchpad.formatToolUsageForPrompt()
|
|
103
|
+
);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const totalTime = Date.now() - ctx.startTime;
|
|
109
|
+
yield {
|
|
110
|
+
type: 'done',
|
|
111
|
+
answer: `Error: ${formatUserFacingError(errorMessage)}`,
|
|
112
|
+
toolCalls: ctx.scratchpad.getToolCallRecords(),
|
|
113
|
+
iterations: ctx.iteration,
|
|
114
|
+
totalTime,
|
|
115
|
+
tokenUsage: ctx.tokenCounter.getUsage(),
|
|
116
|
+
tokensPerSecond: ctx.tokenCounter.getTokensPerSecond(totalTime),
|
|
117
|
+
};
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
ctx.tokenCounter.add(usage);
|
|
123
|
+
const responseText = typeof response === 'string' ? response : extractTextContent(response);
|
|
124
|
+
|
|
125
|
+
// Emit thinking if there are also tool calls (skip whitespace-only responses)
|
|
126
|
+
if (responseText?.trim() && typeof response !== 'string' && hasToolCalls(response)) {
|
|
127
|
+
const trimmedText = responseText.trim();
|
|
128
|
+
ctx.scratchpad.addThinking(trimmedText);
|
|
129
|
+
yield { type: 'thinking', message: trimmedText };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// No tool calls = final answer is in this response
|
|
133
|
+
if (typeof response === 'string' || !hasToolCalls(response)) {
|
|
134
|
+
yield* this.handleDirectResponse(responseText ?? '', ctx);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Execute tools and add results to scratchpad (response is AIMessage here)
|
|
139
|
+
for await (const event of this.toolExecutor.executeAll(response, ctx)) {
|
|
140
|
+
yield event;
|
|
141
|
+
if (event.type === 'tool_denied') {
|
|
142
|
+
const totalTime = Date.now() - ctx.startTime;
|
|
143
|
+
yield {
|
|
144
|
+
type: 'done',
|
|
145
|
+
answer: '',
|
|
146
|
+
toolCalls: ctx.scratchpad.getToolCallRecords(),
|
|
147
|
+
iterations: ctx.iteration,
|
|
148
|
+
totalTime,
|
|
149
|
+
tokenUsage: ctx.tokenCounter.getUsage(),
|
|
150
|
+
tokensPerSecond: ctx.tokenCounter.getTokensPerSecond(totalTime),
|
|
151
|
+
};
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
yield* this.manageContextThreshold(ctx, query);
|
|
156
|
+
|
|
157
|
+
// Build iteration prompt with full tool results (Anthropic-style)
|
|
158
|
+
currentPrompt = buildIterationPrompt(
|
|
159
|
+
query,
|
|
160
|
+
ctx.scratchpad.getToolResults(),
|
|
161
|
+
ctx.scratchpad.formatToolUsageForPrompt()
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Max iterations reached with no final response
|
|
166
|
+
const totalTime = Date.now() - ctx.startTime;
|
|
167
|
+
yield {
|
|
168
|
+
type: 'done',
|
|
169
|
+
answer: `Reached maximum iterations (${this.maxIterations}). I was unable to complete the research in the allotted steps.`,
|
|
170
|
+
toolCalls: ctx.scratchpad.getToolCallRecords(),
|
|
171
|
+
iterations: ctx.iteration,
|
|
172
|
+
totalTime,
|
|
173
|
+
tokenUsage: ctx.tokenCounter.getUsage(),
|
|
174
|
+
tokensPerSecond: ctx.tokenCounter.getTokensPerSecond(totalTime),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Call the LLM with the current prompt.
|
|
180
|
+
*/
|
|
181
|
+
private async callModel(prompt: string, useTools: boolean = true): Promise<{ response: AIMessage | string; usage?: TokenUsage }> {
|
|
182
|
+
const result = await callLlm(prompt, {
|
|
183
|
+
model: this.model,
|
|
184
|
+
systemPrompt: this.systemPrompt,
|
|
185
|
+
tools: useTools ? this.tools : undefined,
|
|
186
|
+
signal: this.signal,
|
|
187
|
+
});
|
|
188
|
+
return { response: result.response, usage: result.usage };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Emit the response text as the final answer.
|
|
193
|
+
*/
|
|
194
|
+
private async *handleDirectResponse(
|
|
195
|
+
responseText: string,
|
|
196
|
+
ctx: RunContext
|
|
197
|
+
): AsyncGenerator<AgentEvent, void> {
|
|
198
|
+
const totalTime = Date.now() - ctx.startTime;
|
|
199
|
+
yield {
|
|
200
|
+
type: 'done',
|
|
201
|
+
answer: responseText,
|
|
202
|
+
toolCalls: ctx.scratchpad.getToolCallRecords(),
|
|
203
|
+
iterations: ctx.iteration,
|
|
204
|
+
totalTime,
|
|
205
|
+
tokenUsage: ctx.tokenCounter.getUsage(),
|
|
206
|
+
tokensPerSecond: ctx.tokenCounter.getTokensPerSecond(totalTime),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Clear oldest tool results if context size exceeds threshold.
|
|
212
|
+
*/
|
|
213
|
+
private async *manageContextThreshold(
|
|
214
|
+
ctx: RunContext,
|
|
215
|
+
query: string,
|
|
216
|
+
): AsyncGenerator<ContextClearedEvent, void> {
|
|
217
|
+
const fullToolResults = ctx.scratchpad.getToolResults();
|
|
218
|
+
const estimatedContextTokens = estimateTokens(this.systemPrompt + ctx.query + fullToolResults);
|
|
219
|
+
|
|
220
|
+
if (estimatedContextTokens > CONTEXT_THRESHOLD) {
|
|
221
|
+
const clearedCount = ctx.scratchpad.clearOldestToolResults(KEEP_TOOL_USES);
|
|
222
|
+
if (clearedCount > 0) {
|
|
223
|
+
yield { type: 'context_cleared', clearedCount, keptCount: KEEP_TOOL_USES };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Build initial prompt with conversation history context if available
|
|
230
|
+
*/
|
|
231
|
+
private buildInitialPrompt(
|
|
232
|
+
query: string,
|
|
233
|
+
inMemoryChatHistory?: InMemoryChatHistory
|
|
234
|
+
): string {
|
|
235
|
+
if (!inMemoryChatHistory?.hasMessages()) {
|
|
236
|
+
return query;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const recentTurns = inMemoryChatHistory.getRecentTurns();
|
|
240
|
+
if (recentTurns.length === 0) {
|
|
241
|
+
return query;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return buildHistoryContext({
|
|
245
|
+
entries: recentTurns,
|
|
246
|
+
currentMessage: query,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ChannelProfile } from './types.js';
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Channel Profiles — add new channels here
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
const CLI_PROFILE: ChannelProfile = {
|
|
8
|
+
label: 'CLI',
|
|
9
|
+
preamble:
|
|
10
|
+
'Your output is displayed on a command line interface. Keep responses short and concise.\nType /help to see available slash commands for quick market actions.',
|
|
11
|
+
behavior: [
|
|
12
|
+
"Prioritize accuracy over validation - don't cheerfully agree with flawed assumptions",
|
|
13
|
+
'Use professional, objective tone without excessive praise or emotional validation',
|
|
14
|
+
'For research tasks, be thorough but efficient',
|
|
15
|
+
'Avoid over-engineering responses - match the scope of your answer to the question',
|
|
16
|
+
'Never expose raw API internals or ask users to paste JSON - synthesize the data into readable answers',
|
|
17
|
+
'If data is incomplete, answer with what you have without exposing implementation details',
|
|
18
|
+
],
|
|
19
|
+
responseFormat: [
|
|
20
|
+
'Keep casual responses brief and direct',
|
|
21
|
+
'For research: lead with the key finding and include specific data points',
|
|
22
|
+
'For non-comparative information, prefer plain text or simple lists over tables',
|
|
23
|
+
"Don't narrate your actions or ask leading questions about what the user wants",
|
|
24
|
+
'Do not use markdown headers or *italics* - use **bold** sparingly for emphasis',
|
|
25
|
+
],
|
|
26
|
+
tables: `Use markdown tables. They will be rendered as formatted box tables.
|
|
27
|
+
|
|
28
|
+
STRICT FORMAT - each row must:
|
|
29
|
+
- Start with | and end with |
|
|
30
|
+
- Have no trailing spaces after the final |
|
|
31
|
+
- Use |---| separator (with optional : for alignment)
|
|
32
|
+
|
|
33
|
+
| Ticker | YES | NO | Volume |
|
|
34
|
+
|--------|-------|-------|--------|
|
|
35
|
+
| KXBTC | $0.56 | $0.44 | 12,450 |
|
|
36
|
+
|
|
37
|
+
Keep tables compact:
|
|
38
|
+
- Max 4 columns; prefer multiple small tables over one wide table
|
|
39
|
+
- Headers: 1-3 words max
|
|
40
|
+
- Prices: $0.56 not $0.5600
|
|
41
|
+
- Tickers not full names where possible
|
|
42
|
+
- Numbers compact: 12.5K not 12,500`,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Registry of channel profiles. Add new channels here. */
|
|
46
|
+
const CHANNEL_PROFILES: Record<string, ChannelProfile> = {
|
|
47
|
+
cli: CLI_PROFILE,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Resolve the profile for a channel, falling back to CLI. */
|
|
51
|
+
export function getChannelProfile(channel?: string): ChannelProfile {
|
|
52
|
+
return CHANNEL_PROFILES[channel ?? 'cli'] ?? CLI_PROFILE;
|
|
53
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export { Agent } from './agent.js';
|
|
2
|
+
|
|
3
|
+
export { Scratchpad } from './scratchpad.js';
|
|
4
|
+
|
|
5
|
+
export { getCurrentDate, buildSystemPrompt, buildIterationPrompt, DEFAULT_SYSTEM_PROMPT } from './prompts.js';
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
ApprovalDecision,
|
|
9
|
+
AgentConfig,
|
|
10
|
+
Message,
|
|
11
|
+
AgentEvent,
|
|
12
|
+
ThinkingEvent,
|
|
13
|
+
ToolStartEvent,
|
|
14
|
+
ToolProgressEvent,
|
|
15
|
+
ToolEndEvent,
|
|
16
|
+
ToolErrorEvent,
|
|
17
|
+
ToolApprovalEvent,
|
|
18
|
+
ToolDeniedEvent,
|
|
19
|
+
ToolLimitEvent,
|
|
20
|
+
ContextClearedEvent,
|
|
21
|
+
DoneEvent,
|
|
22
|
+
} from './types.js';
|
|
23
|
+
|
|
24
|
+
export type {
|
|
25
|
+
ToolCallRecord,
|
|
26
|
+
ScratchpadEntry,
|
|
27
|
+
ToolLimitConfig,
|
|
28
|
+
ToolUsageStatus,
|
|
29
|
+
} from './scratchpad.js';
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { buildToolDescriptions } from '../tools/registry.js';
|
|
2
|
+
import { getChannelProfile } from './channels.js';
|
|
3
|
+
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Helper Functions
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Returns the current date formatted for prompts.
|
|
10
|
+
*/
|
|
11
|
+
export function getCurrentDate(): string {
|
|
12
|
+
const options: Intl.DateTimeFormatOptions = {
|
|
13
|
+
weekday: 'long',
|
|
14
|
+
year: 'numeric',
|
|
15
|
+
month: 'long',
|
|
16
|
+
day: 'numeric',
|
|
17
|
+
};
|
|
18
|
+
return new Date().toLocaleDateString('en-US', options);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Group Context
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Context for group chat conversations (e.g., WhatsApp groups).
|
|
27
|
+
*/
|
|
28
|
+
export interface GroupContext {
|
|
29
|
+
/** Display name of the group */
|
|
30
|
+
groupName?: string;
|
|
31
|
+
/** Formatted list of group members */
|
|
32
|
+
membersList?: string;
|
|
33
|
+
/** How the bot was activated in the group */
|
|
34
|
+
activationMode?: 'mention' | 'command' | 'direct';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Default System Prompt (for backward compatibility)
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
export const DEFAULT_SYSTEM_PROMPT = `You are Kalshi Trading Bot CLI, a prediction market research and trading assistant.
|
|
42
|
+
|
|
43
|
+
Current date: ${getCurrentDate()}
|
|
44
|
+
|
|
45
|
+
Your output is displayed on a command line interface. Keep responses short and concise.
|
|
46
|
+
|
|
47
|
+
## Behavior
|
|
48
|
+
|
|
49
|
+
- Prioritize accuracy over validation
|
|
50
|
+
- Use professional, objective tone
|
|
51
|
+
- Be thorough but efficient
|
|
52
|
+
|
|
53
|
+
## Response Format
|
|
54
|
+
|
|
55
|
+
- Keep responses brief and direct
|
|
56
|
+
- For non-comparative information, prefer plain text or simple lists over tables
|
|
57
|
+
- Do not use markdown headers or *italics* - use **bold** sparingly for emphasis
|
|
58
|
+
|
|
59
|
+
## Tables (for comparative/tabular data)
|
|
60
|
+
|
|
61
|
+
Use markdown tables. They will be rendered as formatted box tables.
|
|
62
|
+
|
|
63
|
+
STRICT FORMAT - each row must:
|
|
64
|
+
- Start with | and end with |
|
|
65
|
+
- Have no trailing spaces after the final |
|
|
66
|
+
- Use |---| separator (with optional : for alignment)
|
|
67
|
+
|
|
68
|
+
| Ticker | YES | NO |
|
|
69
|
+
|--------|------|------|
|
|
70
|
+
| KXBTC | $0.56| $0.44|
|
|
71
|
+
|
|
72
|
+
Keep tables compact:
|
|
73
|
+
- Max 3-4 columns; prefer multiple small tables over one wide table
|
|
74
|
+
- Headers: 1-3 words max
|
|
75
|
+
- Numbers compact: $0.56 not $0.5600
|
|
76
|
+
- Omit units in cells if header has them`;
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// System Prompt
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Build the system prompt for the agent.
|
|
84
|
+
* @param model - The model name (used to get appropriate tool descriptions)
|
|
85
|
+
* @param channel - Delivery channel (e.g., 'cli') — selects formatting profile
|
|
86
|
+
*/
|
|
87
|
+
export function buildSystemPrompt(model: string, channel?: string): string {
|
|
88
|
+
const toolDescriptions = buildToolDescriptions(model);
|
|
89
|
+
const profile = getChannelProfile(channel);
|
|
90
|
+
|
|
91
|
+
const behaviorBullets = profile.behavior.map((b) => `- ${b}`).join('\n');
|
|
92
|
+
const formatBullets = profile.responseFormat.map((b) => `- ${b}`).join('\n');
|
|
93
|
+
|
|
94
|
+
const tablesSection = profile.tables
|
|
95
|
+
? `\n## Tables (for comparative/tabular data)\n\n${profile.tables}`
|
|
96
|
+
: '';
|
|
97
|
+
|
|
98
|
+
return `You are Kalshi Trading Bot CLI, an AI-powered prediction market research and trading assistant.
|
|
99
|
+
|
|
100
|
+
Current date: ${getCurrentDate()}
|
|
101
|
+
|
|
102
|
+
${profile.preamble}
|
|
103
|
+
|
|
104
|
+
## Available Tools
|
|
105
|
+
|
|
106
|
+
${toolDescriptions}
|
|
107
|
+
|
|
108
|
+
## Tool Usage Policy
|
|
109
|
+
|
|
110
|
+
- For market data, events, orderbooks, historical data, portfolio info → use kalshi_search
|
|
111
|
+
- For placing, amending, or canceling orders → use kalshi_trade (requires user approval)
|
|
112
|
+
- For a quick portfolio balance + positions check → use portfolio_overview
|
|
113
|
+
- For background research on real-world events behind markets → use web_search or web_fetch
|
|
114
|
+
- For running a live scan to find mispriced markets → use scan_markets (fetches from Kalshi + Octagon, populates DB)
|
|
115
|
+
- For querying existing edge signals already in the database → use edge_query (instant, reads from DB)
|
|
116
|
+
- For positions with current edge, P&L, and bankroll → use portfolio_query
|
|
117
|
+
- For risk gate status, circuit breaker, drawdown → use risk_status
|
|
118
|
+
- For reviewing positions and identifying close (sell) opportunities → use portfolio_review. Present the SELL signals and trade recommendations to the user. Only invoke kalshi_trade after the user has explicitly approved execution of the specific trade(s)
|
|
119
|
+
- IMPORTANT: Whenever the user asks about ANY specific market, event, or ticker — call octagon_report. This applies to deep dives, research, analysis, "tell me about", "what do you think of", price checks, edge questions, or any query that references a market. The Octagon report provides model probabilities, price drivers, catalysts, and sources that make your answer dramatically better. Call it alongside kalshi_search by default. Pick the most relevant ticker yourself — never ask the user to choose. Pass a full Kalshi URL when possible (like https://kalshi.com/markets/kxcpiyoy/inflation/kxcpiyoy-26mar) — construct it from kalshi_search results using the series_ticker, event_ticker, and ticker fields. The only exceptions are pure account queries (balance, orders, positions) or trade execution
|
|
120
|
+
- The edge/portfolio/risk/octagon tools query the local database populated by the scan loop
|
|
121
|
+
- NEVER place trades without explicit user confirmation
|
|
122
|
+
- Prices are in cents: $0.56 = 56 cents = 56% implied probability
|
|
123
|
+
- YES price + NO price ≈ 100 cents (they are complements)
|
|
124
|
+
- CRITICAL TABLE FORMAT: When Octagon data is available (look for octagon_report in kalshi_search results — it contains outcome_probabilities with per-market model_probability and market_probability), you MUST show a SINGLE unified table. Match each market ticker to its Octagon outcome by market_ticker field, then show:
|
|
125
|
+
| Ticker | Market | Model | Edge | Vol |
|
|
126
|
+
|--------|--------|-------|------|-----|
|
|
127
|
+
| KXTESLA-26-Q1-330000 | 72% | 95% | +23% | 67.5K |
|
|
128
|
+
| KXTESLA-26-Q1-340000 | 65% | 65% | 0% | 92.1K |
|
|
129
|
+
Market = YES price as %. Model = model_probability from octagon outcome_probabilities. Edge = Model - Market.
|
|
130
|
+
NEVER show a table without Model and Edge columns when Octagon data is present. NEVER show Octagon data in a separate section — it must be in the same table as market data
|
|
131
|
+
|
|
132
|
+
## Behavior
|
|
133
|
+
|
|
134
|
+
${behaviorBullets}
|
|
135
|
+
|
|
136
|
+
## Response Format
|
|
137
|
+
|
|
138
|
+
${formatBullets}${tablesSection}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ============================================================================
|
|
142
|
+
// User Prompts
|
|
143
|
+
// ============================================================================
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Build user prompt for agent iteration with full tool results.
|
|
147
|
+
* Anthropic-style: full results in context for accurate decision-making.
|
|
148
|
+
*
|
|
149
|
+
* @param originalQuery - The user's original query
|
|
150
|
+
* @param fullToolResults - Formatted full tool results
|
|
151
|
+
* @param toolUsageStatus - Optional tool usage status for graceful exit mechanism
|
|
152
|
+
*/
|
|
153
|
+
export function buildIterationPrompt(
|
|
154
|
+
originalQuery: string,
|
|
155
|
+
fullToolResults: string,
|
|
156
|
+
toolUsageStatus?: string | null
|
|
157
|
+
): string {
|
|
158
|
+
let prompt = `Query: ${originalQuery}`;
|
|
159
|
+
|
|
160
|
+
if (fullToolResults.trim()) {
|
|
161
|
+
prompt += `\n\nData retrieved from tool calls:\n${fullToolResults}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (toolUsageStatus) {
|
|
165
|
+
prompt += `\n\n${toolUsageStatus}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
prompt += `\n\nContinue working toward answering the query. When you have gathered sufficient data to answer, write your complete answer directly and do not call more tools. NEVER guess at URLs - use ONLY URLs visible in tool results.`;
|
|
169
|
+
|
|
170
|
+
return prompt;
|
|
171
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Scratchpad } from './scratchpad.js';
|
|
2
|
+
import { TokenCounter } from './token-counter.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mutable state for a single agent run.
|
|
6
|
+
*/
|
|
7
|
+
export interface RunContext {
|
|
8
|
+
readonly query: string;
|
|
9
|
+
readonly scratchpad: Scratchpad;
|
|
10
|
+
readonly tokenCounter: TokenCounter;
|
|
11
|
+
readonly startTime: number;
|
|
12
|
+
iteration: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createRunContext(query: string): RunContext {
|
|
16
|
+
return {
|
|
17
|
+
query,
|
|
18
|
+
scratchpad: new Scratchpad(query),
|
|
19
|
+
tokenCounter: new TokenCounter(),
|
|
20
|
+
startTime: Date.now(),
|
|
21
|
+
iteration: 0,
|
|
22
|
+
};
|
|
23
|
+
}
|