@blockrun/franklin 3.6.13 → 3.6.15

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.
@@ -367,8 +367,14 @@ function formatCompactSummary(raw) {
367
367
  }
368
368
  /**
369
369
  * Pick a cheaper/faster model for compaction to save cost.
370
+ * If the primary model is free (NVIDIA), compaction also stays free
371
+ * so users don't get silent charges when their context fills up.
370
372
  */
371
373
  function pickCompactionModel(primaryModel) {
374
+ // Free parent → free compaction (no silent charge)
375
+ if (primaryModel.startsWith('nvidia/') || primaryModel === 'blockrun/free') {
376
+ return 'nvidia/nemotron-ultra-253b';
377
+ }
372
378
  // Use cheapest capable model for summarization to save cost
373
379
  // Tier down: opus/pro → sonnet, sonnet → haiku, everything else → flash (cheapest capable)
374
380
  if (primaryModel.includes('opus') || primaryModel.includes('pro')) {
@@ -380,7 +386,7 @@ function pickCompactionModel(primaryModel) {
380
386
  if (primaryModel.includes('haiku') || primaryModel.includes('mini') || primaryModel.includes('nano')) {
381
387
  return 'google/gemini-2.5-flash'; // Cheapest capable model
382
388
  }
383
- // Free/unknown models — use flash
389
+ // Unknown models — use flash
384
390
  return 'google/gemini-2.5-flash';
385
391
  }
386
392
  /**
@@ -72,22 +72,12 @@ export async function startCommand(options) {
72
72
  }
73
73
  printBanner(version);
74
74
  const workDir = process.cwd();
75
- // Show session info immediately, fetch balance in background
76
- // Model is shown in the live status bar no static line needed.
77
- console.log(chalk.dim(` Wallet: ${walletAddress || 'not set'}`));
78
- console.log(chalk.dim(` Dir: ${workDir}`));
79
- // First-run tip: show if no config file exists yet
80
- if (!configModel && !options.model) {
81
- console.log(chalk.dim(`\n Tip: /model to switch models · /compact to save tokens · /help for all commands`));
82
- }
83
- // Welcome message — show things Hermes/OpenClaw can't do.
84
- // Only on first run or when no model is configured (new user indicator).
85
- // After the user's first session, the tip fades and they go straight to the prompt.
86
- console.log('');
87
- console.log(chalk.dim(' Try something only Franklin can do:'));
88
- console.log(chalk.dim(' ') + chalk.hex('#FFD700')('"what\'s BTC looking like today?"') + chalk.dim(' ← live market data'));
89
- console.log(chalk.dim(' ') + chalk.hex('#60A5FA')('"generate a logo for my startup"') + chalk.dim(' ← AI image gen'));
90
- console.log(chalk.dim(' Code with 55+ models. No API keys. Pay per use.'));
75
+ // Session info — aligned, minimal. Model + balance live in the input bar below.
76
+ const short = (s) => s.length > 14 ? s.slice(0, 6) + '...' + s.slice(-4) : s;
77
+ console.log(chalk.dim(' Wallet: ') + (walletAddress ? short(walletAddress) : chalk.yellow('not set')));
78
+ console.log(chalk.dim(' Dir: ') + workDir);
79
+ console.log(chalk.dim(' Dashboard: ') + chalk.cyan('franklin panel') + chalk.dim(' http://localhost:3100'));
80
+ console.log(chalk.dim(' Help: ') + chalk.cyan('/help'));
91
81
  console.log('');
92
82
  // Balance fetcher — used at startup and after each turn
93
83
  const fetchBalance = async () => {
@@ -142,10 +132,11 @@ export async function startCommand(options) {
142
132
  }
143
133
  }
144
134
  // Build capabilities (built-in + MCP + sub-agent + MoA)
145
- const subAgent = createSubAgentCapability(apiUrl, chain, allCapabilities);
135
+ // Pass parent model so sub-agents inherit it (no silent paid spawns from free parents)
136
+ const subAgent = createSubAgentCapability(apiUrl, chain, allCapabilities, model);
146
137
  // Register MoA tool config (needs API URL for parallel model queries)
147
138
  const { registerMoAConfig } = await import('../tools/moa.js');
148
- registerMoAConfig(apiUrl, chain);
139
+ registerMoAConfig(apiUrl, chain, model);
149
140
  const capabilities = [...allCapabilities, ...mcpTools, subAgent];
150
141
  // Validate tool descriptions (self-evolution: detect SearchX-style description bugs)
151
142
  if (options.debug) {
@@ -13,4 +13,4 @@
13
13
  import type { CapabilityHandler } from '../agent/types.js';
14
14
  export declare const moaCapability: CapabilityHandler;
15
15
  /** Register the API URL for MoA tool (called during agent setup). */
16
- export declare function registerMoAConfig(apiUrl: string, chain: 'base' | 'solana'): void;
16
+ export declare function registerMoAConfig(apiUrl: string, chain: 'base' | 'solana', parentModel?: string): void;
package/dist/tools/moa.js CHANGED
@@ -31,13 +31,18 @@ const REFERENCE_TIMEOUT_MS = 60_000;
31
31
  // These will be injected at registration time
32
32
  let registeredApiUrl = '';
33
33
  let registeredChain = 'base';
34
+ let registeredParentModel = '';
34
35
  async function execute(input, ctx) {
35
36
  const { prompt, models, aggregator, include_reasoning } = input;
36
37
  if (!prompt) {
37
38
  return { output: 'Error: prompt is required', isError: true };
38
39
  }
39
40
  const referenceModels = models || REFERENCE_MODELS;
40
- const aggregatorModel = aggregator || AGGREGATOR_MODEL;
41
+ // If parent agent is on a free model, default aggregator to a free model too
42
+ // so MoA doesn't silently charge the user. Explicit `aggregator` arg wins.
43
+ const parentIsFree = registeredParentModel.startsWith('nvidia/') ||
44
+ registeredParentModel === 'blockrun/free';
45
+ const aggregatorModel = aggregator || (parentIsFree ? 'nvidia/nemotron-ultra-253b' : AGGREGATOR_MODEL);
41
46
  const client = new ModelClient({
42
47
  apiUrl: registeredApiUrl,
43
48
  chain: registeredChain,
@@ -167,7 +172,9 @@ Parameters:
167
172
  concurrent: true,
168
173
  };
169
174
  /** Register the API URL for MoA tool (called during agent setup). */
170
- export function registerMoAConfig(apiUrl, chain) {
175
+ export function registerMoAConfig(apiUrl, chain, parentModel) {
171
176
  registeredApiUrl = apiUrl;
172
177
  registeredChain = chain;
178
+ if (parentModel)
179
+ registeredParentModel = parentModel;
173
180
  }
@@ -2,4 +2,4 @@
2
2
  * SubAgent capability — spawn a child agent for independent tasks.
3
3
  */
4
4
  import type { CapabilityHandler } from '../agent/types.js';
5
- export declare function createSubAgentCapability(apiUrl: string, chain: 'base' | 'solana', capabilities: CapabilityHandler[]): CapabilityHandler;
5
+ export declare function createSubAgentCapability(apiUrl: string, chain: 'base' | 'solana', capabilities: CapabilityHandler[], parentModel?: string): CapabilityHandler;
@@ -6,12 +6,31 @@ import { assembleInstructions } from '../agent/context.js';
6
6
  // These will be injected at registration time
7
7
  let registeredApiUrl = '';
8
8
  let registeredChain = 'base';
9
+ let registeredParentModel = '';
9
10
  let registeredCapabilities = [];
11
+ // Heuristic: which model IDs are free?
12
+ function isFreeModel(m) {
13
+ return m.startsWith('nvidia/') || m === 'blockrun/free' || m === '';
14
+ }
10
15
  async function execute(input, ctx) {
11
16
  const { prompt, description, model } = input;
12
17
  if (!prompt) {
13
18
  return { output: 'Error: prompt is required', isError: true };
14
19
  }
20
+ // Resolve which model the sub-agent will actually run on
21
+ const subModel = model || registeredParentModel || 'nvidia/nemotron-ultra-253b';
22
+ // Cost gate: if parent is free but sub-agent wants paid, ask user first.
23
+ // Prevents silent charges when the agent decides to spawn a more capable sub-agent.
24
+ if (isFreeModel(registeredParentModel) && !isFreeModel(subModel) && ctx.onAskUser) {
25
+ const shortLabel = subModel.split('/').pop() || subModel;
26
+ const answer = await ctx.onAskUser(`Sub-agent wants to use ${shortLabel} (paid). Approve?`, ['y', 'n']);
27
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
28
+ return {
29
+ output: `Sub-agent skipped — user declined paid model (${shortLabel}). Retry with a free model like nemotron.`,
30
+ isError: true,
31
+ };
32
+ }
33
+ }
15
34
  const client = new ModelClient({
16
35
  apiUrl: registeredApiUrl,
17
36
  chain: registeredChain,
@@ -54,7 +73,7 @@ async function execute(input, ctx) {
54
73
  }
55
74
  turn++;
56
75
  const { content: parts } = await client.complete({
57
- model: model || 'anthropic/claude-sonnet-4.6',
76
+ model: subModel,
58
77
  messages: history,
59
78
  system: systemPrompt,
60
79
  tools: toolDefs,
@@ -107,10 +126,12 @@ async function execute(input, ctx) {
107
126
  output: finalText || `[${label}] completed after ${turn} turn(s) with no text output.`,
108
127
  };
109
128
  }
110
- export function createSubAgentCapability(apiUrl, chain, capabilities) {
129
+ export function createSubAgentCapability(apiUrl, chain, capabilities, parentModel) {
111
130
  registeredApiUrl = apiUrl;
112
131
  registeredChain = chain;
113
132
  registeredCapabilities = capabilities;
133
+ if (parentModel)
134
+ registeredParentModel = parentModel;
114
135
  return {
115
136
  spec: {
116
137
  name: 'Agent',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.6.13",
3
+ "version": "3.6.15",
4
4
  "description": "Franklin — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {