@blockrun/franklin 3.8.2 → 3.8.3
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/README.md +23 -36
- package/dist/agent/commands.js +1 -1
- package/dist/agent/llm.d.ts +6 -0
- package/dist/agent/llm.js +103 -14
- package/dist/agent/loop.d.ts +9 -0
- package/dist/agent/loop.js +85 -0
- package/dist/agent/think-tag-stripper.d.ts +27 -0
- package/dist/agent/think-tag-stripper.js +75 -0
- package/dist/agent/tokens.js +2 -1
- package/dist/agent/types.d.ts +7 -0
- package/dist/brain/index.d.ts +1 -1
- package/dist/brain/index.js +1 -1
- package/dist/brain/store.d.ts +13 -1
- package/dist/brain/store.js +74 -5
- package/dist/channel/telegram.d.ts +46 -0
- package/dist/channel/telegram.js +367 -0
- package/dist/commands/migrate.d.ts +5 -3
- package/dist/commands/migrate.js +17 -15
- package/dist/commands/stats.js +1 -1
- package/dist/commands/telegram.d.ts +15 -0
- package/dist/commands/telegram.js +95 -0
- package/dist/content/library.js +2 -2
- package/dist/index.js +9 -0
- package/dist/panel/html.js +1 -1
- package/dist/router/index.js +5 -5
- package/dist/session/storage.d.ts +12 -0
- package/dist/session/storage.js +11 -0
- package/dist/social/ai.d.ts +3 -2
- package/dist/social/ai.js +3 -2
- package/dist/stats/insights.d.ts +1 -1
- package/dist/stats/tracker.js +1 -1
- package/dist/tools/content-execute.d.ts +1 -1
- package/dist/tools/content-execute.js +1 -1
- package/dist/tools/index.js +11 -3
- package/dist/tools/memory.d.ts +16 -0
- package/dist/tools/memory.js +86 -0
- package/dist/tools/trading-execute.d.ts +2 -2
- package/dist/tools/trading-execute.js +2 -2
- package/dist/tools/videogen.d.ts +17 -0
- package/dist/tools/videogen.js +237 -0
- package/dist/trading/trade-log.d.ts +2 -2
- package/dist/trading/trade-log.js +2 -2
- package/dist/ui/app.js +38 -3
- package/dist/ui/markdown.d.ts +16 -0
- package/dist/ui/markdown.js +26 -2
- package/package.json +5 -2
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `franklin telegram` — start the Telegram ingress bot.
|
|
3
|
+
*
|
|
4
|
+
* Designed to run on a server / always-on laptop. Reads the bot token and
|
|
5
|
+
* owner id from env (or falls back to ~/.blockrun/config). Uses trust-mode
|
|
6
|
+
* permissions because the operator is remote — there's no terminal prompt
|
|
7
|
+
* they can answer per tool call. The owner lock in `runTelegramBot` is the
|
|
8
|
+
* real security boundary.
|
|
9
|
+
*/
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { loadChain, API_URLS } from '../config.js';
|
|
12
|
+
import { assembleInstructions } from '../agent/context.js';
|
|
13
|
+
import { allCapabilities } from '../tools/index.js';
|
|
14
|
+
import { loadConfig } from './config.js';
|
|
15
|
+
import { runTelegramBot } from '../channel/telegram.js';
|
|
16
|
+
import { findLatestSessionByChannel } from '../session/storage.js';
|
|
17
|
+
export async function telegramCommand(opts) {
|
|
18
|
+
const token = process.env.TELEGRAM_BOT_TOKEN;
|
|
19
|
+
const ownerRaw = process.env.TELEGRAM_OWNER_ID;
|
|
20
|
+
if (!token || !ownerRaw) {
|
|
21
|
+
console.error(chalk.red('Missing Telegram config.'));
|
|
22
|
+
console.error(chalk.dim('\nSet two env vars before running `franklin telegram`:\n' +
|
|
23
|
+
' TELEGRAM_BOT_TOKEN=<from @BotFather>\n' +
|
|
24
|
+
' TELEGRAM_OWNER_ID=<your numeric Telegram user id>\n\n' +
|
|
25
|
+
'Tip: message @userinfobot on Telegram to get your user id.'));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const ownerId = parseInt(ownerRaw, 10);
|
|
29
|
+
if (!Number.isFinite(ownerId) || ownerId <= 0) {
|
|
30
|
+
console.error(chalk.red(`TELEGRAM_OWNER_ID must be a positive integer, got: ${ownerRaw}`));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
const chain = loadChain();
|
|
34
|
+
const apiUrl = API_URLS[chain];
|
|
35
|
+
const config = loadConfig();
|
|
36
|
+
// Model: --model flag > config default > free default.
|
|
37
|
+
const model = opts.model ||
|
|
38
|
+
config['default-model'] ||
|
|
39
|
+
'nvidia/nemotron-ultra-253b';
|
|
40
|
+
const workingDir = process.cwd();
|
|
41
|
+
const systemInstructions = assembleInstructions(workingDir, model);
|
|
42
|
+
// Resume the most recent session tagged for THIS owner so a process
|
|
43
|
+
// restart doesn't drop the conversation. First run has no prior session
|
|
44
|
+
// and starts fresh. `/new` will lay down a new session ID within the
|
|
45
|
+
// running process; the next restart picks up the newest one again.
|
|
46
|
+
const channelTag = `telegram:${ownerId}`;
|
|
47
|
+
const prior = findLatestSessionByChannel(channelTag);
|
|
48
|
+
if (prior) {
|
|
49
|
+
console.log(chalk.dim(` resuming session ${prior.id} (${prior.messageCount} msgs, ` +
|
|
50
|
+
`last update ${new Date(prior.updatedAt).toLocaleString()})`));
|
|
51
|
+
}
|
|
52
|
+
const agentConfig = {
|
|
53
|
+
model,
|
|
54
|
+
apiUrl,
|
|
55
|
+
chain,
|
|
56
|
+
systemInstructions,
|
|
57
|
+
capabilities: allCapabilities,
|
|
58
|
+
workingDir,
|
|
59
|
+
// No interactive terminal for permission prompts — remote operator can't
|
|
60
|
+
// answer y/n per tool. The Telegram owner lock is the security boundary.
|
|
61
|
+
permissionMode: 'trust',
|
|
62
|
+
debug: opts.debug,
|
|
63
|
+
sessionChannel: channelTag,
|
|
64
|
+
resumeSessionId: prior?.id,
|
|
65
|
+
};
|
|
66
|
+
console.log(chalk.bold.cyan('Franklin Telegram bot'));
|
|
67
|
+
console.log(chalk.dim(` chain: ${chain}`));
|
|
68
|
+
console.log(chalk.dim(` model: ${model}`));
|
|
69
|
+
console.log(chalk.dim(` owner: ${ownerId}`));
|
|
70
|
+
console.log(chalk.yellow(' permission mode: trust — every tool the model picks will execute ' +
|
|
71
|
+
'without confirmation. The owner lock is your only gate.\n'));
|
|
72
|
+
// SIGINT → stop the bot cleanly. Wrap runTelegramBot so a second Ctrl-C
|
|
73
|
+
// force-exits if the cleanup path hangs.
|
|
74
|
+
let exitAttempts = 0;
|
|
75
|
+
process.on('SIGINT', () => {
|
|
76
|
+
exitAttempts++;
|
|
77
|
+
if (exitAttempts === 1) {
|
|
78
|
+
console.log(chalk.dim('\nStopping… (press Ctrl-C again to force)'));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
process.exit(130);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
try {
|
|
85
|
+
await runTelegramBot(agentConfig, {
|
|
86
|
+
token,
|
|
87
|
+
ownerId,
|
|
88
|
+
log: (line) => console.log(chalk.dim(line)),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
console.error(chalk.red(`Telegram bot failed: ${err.message}`));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
package/dist/content/library.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* risk engine, store.ts is the persistence adapter, and tools/content-execute.ts
|
|
6
6
|
* wires everything into agent-facing capabilities.
|
|
7
7
|
*
|
|
8
|
-
* Why this exists:
|
|
9
|
-
* sessions. If you start drafting a podcast episode Monday and come back
|
|
8
|
+
* Why this exists: stateless coding agents can't carry a content project
|
|
9
|
+
* across sessions. If you start drafting a podcast episode Monday and come back
|
|
10
10
|
* Wednesday, there's no concept of *the same* piece of work. Franklin tracks
|
|
11
11
|
* outline → drafts → assets → distribution as one durable object and lets
|
|
12
12
|
* the agent spend USDC (image generation, audio, stock footage) against a
|
package/dist/index.js
CHANGED
|
@@ -166,6 +166,15 @@ program
|
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
+
program
|
|
170
|
+
.command('telegram')
|
|
171
|
+
.description('Drive Franklin from Telegram (requires TELEGRAM_BOT_TOKEN + TELEGRAM_OWNER_ID env vars)')
|
|
172
|
+
.option('-m, --model <model>', 'Model to use (default from config)')
|
|
173
|
+
.option('--debug', 'Enable debug logging')
|
|
174
|
+
.action(async (opts) => {
|
|
175
|
+
const { telegramCommand } = await import('./commands/telegram.js');
|
|
176
|
+
await telegramCommand(opts);
|
|
177
|
+
});
|
|
169
178
|
program
|
|
170
179
|
.command('migrate')
|
|
171
180
|
.description('Import preferences and MCP servers from existing AI agent configs')
|
package/dist/panel/html.js
CHANGED
|
@@ -423,7 +423,7 @@ a:hover { text-decoration:underline; }
|
|
|
423
423
|
<div class="savings-hero" id="savings-hero" style="display:none">
|
|
424
424
|
<div>
|
|
425
425
|
<div class="savings-detail">
|
|
426
|
-
<div class="label">Saved vs
|
|
426
|
+
<div class="label">Saved vs Opus tier</div>
|
|
427
427
|
</div>
|
|
428
428
|
<div class="savings-amount" id="savings-amount">—</div>
|
|
429
429
|
<div class="savings-detail">
|
package/dist/router/index.js
CHANGED
|
@@ -33,10 +33,10 @@ function loadLearnedWeights() {
|
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
35
35
|
// ─── Tier Model Configs ───
|
|
36
|
-
// Agent-first defaults.
|
|
37
|
-
// tool-use agent work; cheap models keep derailing on simple agent
|
|
38
|
-
// Each tier's fallback ends with a cheaper option so payment/quota
|
|
39
|
-
// don't strand users on equally expensive alternatives.
|
|
36
|
+
// Agent-first defaults. Sonnet-tier models are the current sweet spot for
|
|
37
|
+
// multi-step tool-use agent work; cheap models keep derailing on simple agent
|
|
38
|
+
// loops. Each tier's fallback ends with a cheaper option so payment/quota
|
|
39
|
+
// failures don't strand users on equally expensive alternatives.
|
|
40
40
|
const AUTO_TIERS = {
|
|
41
41
|
SIMPLE: {
|
|
42
42
|
primary: 'google/gemini-2.5-flash',
|
|
@@ -282,7 +282,7 @@ export function routeRequest(prompt, profile = 'auto') {
|
|
|
282
282
|
// Auto profile bypasses learned routing. The learned Elo scores grow with
|
|
283
283
|
// usage volume rather than pure quality, which biased the router toward
|
|
284
284
|
// cheap/weak models on agentic work. Classic AUTO_TIERS defaults are
|
|
285
|
-
// agent-tuned (
|
|
285
|
+
// agent-tuned (Sonnet-tier backbone) and more predictable for users.
|
|
286
286
|
if (profile === 'auto') {
|
|
287
287
|
return classicRouteRequest(prompt, profile);
|
|
288
288
|
}
|
|
@@ -15,6 +15,12 @@ export interface SessionMeta {
|
|
|
15
15
|
outputTokens?: number;
|
|
16
16
|
costUsd?: number;
|
|
17
17
|
savedVsOpusUsd?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Origin channel tag. Unset for regular CLI sessions; set to a string like
|
|
20
|
+
* `telegram:<ownerId>` when the session was started by a non-CLI driver.
|
|
21
|
+
* Lets findLatestSessionByChannel pick up the right session on bot restart.
|
|
22
|
+
*/
|
|
23
|
+
channel?: string;
|
|
18
24
|
}
|
|
19
25
|
/** Get the absolute path to a session's JSONL file (for external readers like search). */
|
|
20
26
|
export declare function getSessionFilePath(id: string): string;
|
|
@@ -42,6 +48,12 @@ export declare function loadSessionHistory(sessionId: string): Dialogue[];
|
|
|
42
48
|
* List all saved sessions, newest first.
|
|
43
49
|
*/
|
|
44
50
|
export declare function listSessions(): SessionMeta[];
|
|
51
|
+
/**
|
|
52
|
+
* Find the latest saved session tagged with a given channel (e.g.
|
|
53
|
+
* `telegram:12345`). Used by non-CLI drivers to resume across process
|
|
54
|
+
* restarts. Returns undefined when no matching session exists.
|
|
55
|
+
*/
|
|
56
|
+
export declare function findLatestSessionByChannel(channel: string): SessionMeta | undefined;
|
|
45
57
|
/**
|
|
46
58
|
* Prune old sessions beyond MAX_SESSIONS.
|
|
47
59
|
*/
|
package/dist/session/storage.js
CHANGED
|
@@ -92,6 +92,9 @@ export function updateSessionMeta(sessionId, meta) {
|
|
|
92
92
|
outputTokens: meta.outputTokens ?? existing?.outputTokens ?? 0,
|
|
93
93
|
costUsd: meta.costUsd ?? existing?.costUsd ?? 0,
|
|
94
94
|
savedVsOpusUsd: meta.savedVsOpusUsd ?? existing?.savedVsOpusUsd ?? 0,
|
|
95
|
+
...(meta.channel !== undefined || existing?.channel !== undefined
|
|
96
|
+
? { channel: meta.channel ?? existing?.channel }
|
|
97
|
+
: {}),
|
|
95
98
|
};
|
|
96
99
|
// Atomic write: tmp file + rename. Prevents corruption when parent
|
|
97
100
|
// and sub-agent update the same session meta concurrently.
|
|
@@ -176,6 +179,14 @@ export function listSessions() {
|
|
|
176
179
|
return [];
|
|
177
180
|
}
|
|
178
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Find the latest saved session tagged with a given channel (e.g.
|
|
184
|
+
* `telegram:12345`). Used by non-CLI drivers to resume across process
|
|
185
|
+
* restarts. Returns undefined when no matching session exists.
|
|
186
|
+
*/
|
|
187
|
+
export function findLatestSessionByChannel(channel) {
|
|
188
|
+
return listSessions().find(m => m.channel === channel);
|
|
189
|
+
}
|
|
179
190
|
/**
|
|
180
191
|
* Prune old sessions beyond MAX_SESSIONS.
|
|
181
192
|
*/
|
package/dist/social/ai.d.ts
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Key improvements over social-bot:
|
|
9
9
|
* - Uses Franklin's multi-model router (tier-based: free / cheap / premium)
|
|
10
|
-
* instead of
|
|
11
|
-
* can run on free NVIDIA models, high-value leads can escalate to
|
|
10
|
+
* instead of hardcoding one model for every call — throwaway replies
|
|
11
|
+
* can run on free NVIDIA models, high-value leads can escalate to the
|
|
12
|
+
* top tier.
|
|
12
13
|
* - x402 payment flow handled by ModelClient — no Anthropic billing relationship.
|
|
13
14
|
* - SKIP detection lives in the caller so we can commit a 'skipped' record
|
|
14
15
|
* for visibility in stats.
|
package/dist/social/ai.js
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Key improvements over social-bot:
|
|
9
9
|
* - Uses Franklin's multi-model router (tier-based: free / cheap / premium)
|
|
10
|
-
* instead of
|
|
11
|
-
* can run on free NVIDIA models, high-value leads can escalate to
|
|
10
|
+
* instead of hardcoding one model for every call — throwaway replies
|
|
11
|
+
* can run on free NVIDIA models, high-value leads can escalate to the
|
|
12
|
+
* top tier.
|
|
12
13
|
* - x402 payment flow handled by ModelClient — no Anthropic billing relationship.
|
|
13
14
|
* - SKIP detection lives in the caller so we can commit a 'skipped' record
|
|
14
15
|
* for visibility in stats.
|
package/dist/stats/insights.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface InsightsReport {
|
|
|
21
21
|
totalInputTokens: number;
|
|
22
22
|
/** Total output tokens in window */
|
|
23
23
|
totalOutputTokens: number;
|
|
24
|
-
/** Savings vs always using
|
|
24
|
+
/** Savings vs always using the Opus-tier baseline */
|
|
25
25
|
savedVsOpusUsd: number;
|
|
26
26
|
/** Per-model breakdown, sorted by cost desc */
|
|
27
27
|
byModel: Array<{
|
package/dist/stats/tracker.js
CHANGED
|
@@ -207,7 +207,7 @@ export function recordUsage(model, inputTokens, outputTokens, costUsd, latencyMs
|
|
|
207
207
|
*/
|
|
208
208
|
export function getStatsSummary() {
|
|
209
209
|
const stats = loadStats();
|
|
210
|
-
// Calculate what it would cost with
|
|
210
|
+
// Calculate what it would cost with the Opus-tier baseline
|
|
211
211
|
const opusCost = (stats.totalInputTokens / 1_000_000) * OPUS_PRICING.input +
|
|
212
212
|
(stats.totalOutputTokens / 1_000_000) * OPUS_PRICING.output;
|
|
213
213
|
const saved = opusCost - stats.totalCostUsd;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* ContentShow — dump a single piece's state as actionable markdown
|
|
10
10
|
* ContentList — summary of every piece, newest first
|
|
11
11
|
*
|
|
12
|
-
* This is the surface
|
|
12
|
+
* This is the surface generic coding agents can't cover: durable content
|
|
13
13
|
* state with autonomous spending gating. An agent can spin up "Franklin
|
|
14
14
|
* launch thread" with a $3 budget, hit DALL-E twice for hero images, blow
|
|
15
15
|
* the budget, read the refusal, pick a cheaper model, and continue — all
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* ContentShow — dump a single piece's state as actionable markdown
|
|
10
10
|
* ContentList — summary of every piece, newest first
|
|
11
11
|
*
|
|
12
|
-
* This is the surface
|
|
12
|
+
* This is the surface generic coding agents can't cover: durable content
|
|
13
13
|
* state with autonomous spending gating. An agent can spin up "Franklin
|
|
14
14
|
* launch thread" with a $3 budget, hit DALL-E twice for hero images, blow
|
|
15
15
|
* the budget, read the refusal, pick a cheaper model, and continue — all
|
package/dist/tools/index.js
CHANGED
|
@@ -13,6 +13,8 @@ import { webFetchCapability, clearSessionState as clearWebFetchSessionState } fr
|
|
|
13
13
|
import { webSearchCapability } from './websearch.js';
|
|
14
14
|
import { taskCapability } from './task.js';
|
|
15
15
|
import { createImageGenCapability } from './imagegen.js';
|
|
16
|
+
import { createVideoGenCapability } from './videogen.js';
|
|
17
|
+
import { memoryRecallCapability } from './memory.js';
|
|
16
18
|
import { askUserCapability } from './askuser.js';
|
|
17
19
|
import { tradingSignalCapability, tradingMarketCapability } from './trading.js';
|
|
18
20
|
import { searchXCapability } from './searchx.js';
|
|
@@ -34,7 +36,7 @@ import { loadLibrary as loadContentLibrary, saveLibrary as saveContentLibrary }
|
|
|
34
36
|
// (2.5 positions fully loaded), $900 total exposure cap (keep 10% cash buffer).
|
|
35
37
|
// Live prices from CoinGecko; simulated fills at 10 bps. Portfolio persists
|
|
36
38
|
// to ~/.blockrun/portfolio.json across sessions — that persistence is the
|
|
37
|
-
// whole point of this vertical (
|
|
39
|
+
// whole point of this vertical (stateless coding agents can't carry trading
|
|
38
40
|
// state between runs).
|
|
39
41
|
const DEFAULT_PORTFOLIO_PATH = path.join(os.homedir(), '.blockrun', 'portfolio.json');
|
|
40
42
|
const DEFAULT_TRADE_LOG_PATH = path.join(os.homedir(), '.blockrun', 'trades.jsonl');
|
|
@@ -68,8 +70,8 @@ function buildDefaultTradingCapabilities() {
|
|
|
68
70
|
const defaultTradingCapabilities = buildDefaultTradingCapabilities();
|
|
69
71
|
// ─── Default Content Library ──────────────────────────────────────────────
|
|
70
72
|
// Durable content projects at ~/.blockrun/content.json. Like the portfolio,
|
|
71
|
-
// this is persistent cross-session state — something
|
|
72
|
-
//
|
|
73
|
+
// this is persistent cross-session state — something stateless coding agents
|
|
74
|
+
// structurally can't offer.
|
|
73
75
|
const DEFAULT_CONTENT_PATH = path.join(os.homedir(), '.blockrun', 'content.json');
|
|
74
76
|
// Build a single ContentLibrary instance so both the Content capabilities and
|
|
75
77
|
// the content-aware ImageGen capability share state and persistence.
|
|
@@ -90,6 +92,10 @@ const defaultImageGenCapability = createImageGenCapability({
|
|
|
90
92
|
library: defaultContentLibrary,
|
|
91
93
|
onContentChange: persistContentLibrary,
|
|
92
94
|
});
|
|
95
|
+
const defaultVideoGenCapability = createVideoGenCapability({
|
|
96
|
+
library: defaultContentLibrary,
|
|
97
|
+
onContentChange: persistContentLibrary,
|
|
98
|
+
});
|
|
93
99
|
/**
|
|
94
100
|
* Reset module-level tool state that would otherwise leak between sessions
|
|
95
101
|
* when the same process runs `interactiveSession()` more than once (library
|
|
@@ -112,6 +118,8 @@ export const allCapabilities = [
|
|
|
112
118
|
webSearchCapability,
|
|
113
119
|
taskCapability,
|
|
114
120
|
defaultImageGenCapability,
|
|
121
|
+
defaultVideoGenCapability,
|
|
122
|
+
memoryRecallCapability,
|
|
115
123
|
askUserCapability,
|
|
116
124
|
tradingSignalCapability,
|
|
117
125
|
tradingMarketCapability,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemoryRecall — let the agent query Franklin's Brain explicitly.
|
|
3
|
+
*
|
|
4
|
+
* Auto-recall (in loop.ts) covers the "user just mentioned X" case, but the
|
|
5
|
+
* agent often needs to check memory on its own: "what do I already know
|
|
6
|
+
* about the user's portfolio?", "have I seen this company before?". This
|
|
7
|
+
* tool exposes the Brain's search + observation read path as a first-class
|
|
8
|
+
* capability so the agent can decide when to pull from long-term memory.
|
|
9
|
+
*
|
|
10
|
+
* Read-only. Writing to the Brain happens after-the-fact via
|
|
11
|
+
* extractBrainEntities on session end; we don't expose a write tool here
|
|
12
|
+
* because giving the model direct write access to the knowledge graph
|
|
13
|
+
* invites fabricated "facts" that never saw a real conversation.
|
|
14
|
+
*/
|
|
15
|
+
import type { CapabilityHandler } from '../agent/types.js';
|
|
16
|
+
export declare const memoryRecallCapability: CapabilityHandler;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemoryRecall — let the agent query Franklin's Brain explicitly.
|
|
3
|
+
*
|
|
4
|
+
* Auto-recall (in loop.ts) covers the "user just mentioned X" case, but the
|
|
5
|
+
* agent often needs to check memory on its own: "what do I already know
|
|
6
|
+
* about the user's portfolio?", "have I seen this company before?". This
|
|
7
|
+
* tool exposes the Brain's search + observation read path as a first-class
|
|
8
|
+
* capability so the agent can decide when to pull from long-term memory.
|
|
9
|
+
*
|
|
10
|
+
* Read-only. Writing to the Brain happens after-the-fact via
|
|
11
|
+
* extractBrainEntities on session end; we don't expose a write tool here
|
|
12
|
+
* because giving the model direct write access to the knowledge graph
|
|
13
|
+
* invites fabricated "facts" that never saw a real conversation.
|
|
14
|
+
*/
|
|
15
|
+
import { searchEntities, getEntityObservations, getEntityRelations, loadEntities, getBrainStats, } from '../brain/store.js';
|
|
16
|
+
const MAX_OBS_PER_ENTITY = 5;
|
|
17
|
+
const MAX_REL_PER_ENTITY = 3;
|
|
18
|
+
export const memoryRecallCapability = {
|
|
19
|
+
spec: {
|
|
20
|
+
name: 'MemoryRecall',
|
|
21
|
+
description: "Query Franklin's long-term memory (the Brain) for what it already " +
|
|
22
|
+
"knows about a person, project, company, product, or concept. Use " +
|
|
23
|
+
"this BEFORE asking the user a question the memory might already " +
|
|
24
|
+
"answer, or when deciding how to act toward a named entity. Returns " +
|
|
25
|
+
"matching entities with their observations and relations. " +
|
|
26
|
+
"Read-only — observations are harvested automatically at session end.",
|
|
27
|
+
input_schema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
query: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: 'Name, alias, or substring to search for. Case-insensitive.',
|
|
33
|
+
},
|
|
34
|
+
limit: {
|
|
35
|
+
type: 'number',
|
|
36
|
+
description: 'Max entities to return (default 5, max 15).',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ['query'],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
execute: async (input) => {
|
|
43
|
+
const { query, limit } = input;
|
|
44
|
+
if (!query || !query.trim()) {
|
|
45
|
+
return { output: 'Error: query is required', isError: true };
|
|
46
|
+
}
|
|
47
|
+
const cap = Math.min(Math.max(1, limit ?? 5), 15);
|
|
48
|
+
const hits = searchEntities(query, cap);
|
|
49
|
+
const stats = getBrainStats();
|
|
50
|
+
if (hits.length === 0) {
|
|
51
|
+
return {
|
|
52
|
+
output: `No memory match for "${query}".\n\n` +
|
|
53
|
+
`Brain holds ${stats.entities} entities, ${stats.observations} observations.`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const entities = loadEntities();
|
|
57
|
+
const lines = [`# Memory — ${hits.length} match${hits.length === 1 ? '' : 'es'} for "${query}"`];
|
|
58
|
+
for (const hit of hits) {
|
|
59
|
+
lines.push(`\n## ${hit.name} (${hit.type})`);
|
|
60
|
+
if (hit.aliases.length > 0) {
|
|
61
|
+
lines.push(`aka: ${hit.aliases.join(', ')}`);
|
|
62
|
+
}
|
|
63
|
+
lines.push(`_referenced ${hit.reference_count}×, last seen ${new Date(hit.updated_at).toISOString().slice(0, 10)}_`);
|
|
64
|
+
const obs = getEntityObservations(hit.id)
|
|
65
|
+
.sort((a, b) => b.confidence - a.confidence)
|
|
66
|
+
.slice(0, MAX_OBS_PER_ENTITY);
|
|
67
|
+
if (obs.length > 0) {
|
|
68
|
+
lines.push('\n**Facts**');
|
|
69
|
+
for (const o of obs)
|
|
70
|
+
lines.push(`- ${o.content}`);
|
|
71
|
+
}
|
|
72
|
+
const rels = getEntityRelations(hit.id).slice(0, MAX_REL_PER_ENTITY);
|
|
73
|
+
if (rels.length > 0) {
|
|
74
|
+
lines.push('\n**Relations**');
|
|
75
|
+
for (const r of rels) {
|
|
76
|
+
const otherId = r.from_id === hit.id ? r.to_id : r.from_id;
|
|
77
|
+
const other = entities.find(e => e.id === otherId);
|
|
78
|
+
if (other)
|
|
79
|
+
lines.push(`- ${r.type} → ${other.name}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { output: lines.join('\n') };
|
|
84
|
+
},
|
|
85
|
+
concurrent: true,
|
|
86
|
+
};
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* TradingOpenPosition (buy side), TradingClosePosition (sell side).
|
|
5
5
|
*
|
|
6
6
|
* This is the surface that differentiates Franklin from generic coding
|
|
7
|
-
* agents —
|
|
8
|
-
*
|
|
7
|
+
* agents — stateless tools can't hold a wallet, track positions across
|
|
8
|
+
* sessions, or reason about P&L. Every output here is deliberately
|
|
9
9
|
* information-rich so the agent has the numbers it needs to make the next
|
|
10
10
|
* economic decision (cash left, risk utilization, unrealized vs realized
|
|
11
11
|
* P&L, fill detail) without a follow-up tool call.
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* TradingOpenPosition (buy side), TradingClosePosition (sell side).
|
|
5
5
|
*
|
|
6
6
|
* This is the surface that differentiates Franklin from generic coding
|
|
7
|
-
* agents —
|
|
8
|
-
*
|
|
7
|
+
* agents — stateless tools can't hold a wallet, track positions across
|
|
8
|
+
* sessions, or reason about P&L. Every output here is deliberately
|
|
9
9
|
* information-rich so the agent has the numbers it needs to make the next
|
|
10
10
|
* economic decision (cash left, risk utilization, unrealized vs realized
|
|
11
11
|
* P&L, fill detail) without a follow-up tool call.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Video Generation capability — generate short MP4 videos via the BlockRun
|
|
3
|
+
* /v1/videos/generations endpoint. Uses x402 payment (Base or Solana).
|
|
4
|
+
*
|
|
5
|
+
* Default model `xai/grok-imagine-video` returns an 8-second clip for ~$0.42.
|
|
6
|
+
* The endpoint is synchronous-over-polling: the HTTP connection stays open
|
|
7
|
+
* until the upstream xAI job finishes (typically 20–60s, timeout 180s), so
|
|
8
|
+
* the caller only needs to issue a single POST.
|
|
9
|
+
*/
|
|
10
|
+
import type { CapabilityHandler } from '../agent/types.js';
|
|
11
|
+
import type { ContentLibrary } from '../content/library.js';
|
|
12
|
+
export interface VideoGenDeps {
|
|
13
|
+
library?: ContentLibrary;
|
|
14
|
+
onContentChange?: () => void | Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export declare function createVideoGenCapability(deps?: VideoGenDeps): CapabilityHandler;
|
|
17
|
+
export declare const videoGenCapability: CapabilityHandler;
|