Skip to content

Commit 48675df

Browse files
author
1bcMax
committed
feat: lean startup banner + sub-agent paid-model approval gate
Startup info was verbose marketing copy that hid actionable info. Now: - Shortened wallet address (0x1234...abcd) - Added Dashboard: franklin panel → http://localhost:3100 line - Dropped 'Try something only Franklin can do' block - Dropped 'Code with 55+ models' tagline (already in banner) Sub-agent cost gate: - When the main agent runs on a free model (nvidia/*) and a sub-agent wants to spawn with a paid model, prompt y/n before charging. - User declines → sub-agent skipped, agent gets a clear error message telling it to retry with a free model.
1 parent 12822bd commit 48675df

5 files changed

Lines changed: 57 additions & 39 deletions

File tree

dist/commands/start.js

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,22 +72,12 @@ export async function startCommand(options) {
7272
}
7373
printBanner(version);
7474
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'));
9181
console.log('');
9282
// Balance fetcher — used at startup and after each turn
9383
const fetchBalance = async () => {

dist/tools/subagent.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,29 @@ let registeredApiUrl = '';
88
let registeredChain = 'base';
99
let registeredParentModel = '';
1010
let registeredCapabilities = [];
11+
// Heuristic: which model IDs are free?
12+
function isFreeModel(m) {
13+
return m.startsWith('nvidia/') || m === 'blockrun/free' || m === '';
14+
}
1115
async function execute(input, ctx) {
1216
const { prompt, description, model } = input;
1317
if (!prompt) {
1418
return { output: 'Error: prompt is required', isError: true };
1519
}
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+
}
1634
const client = new ModelClient({
1735
apiUrl: registeredApiUrl,
1836
chain: registeredChain,
@@ -55,9 +73,7 @@ async function execute(input, ctx) {
5573
}
5674
turn++;
5775
const { content: parts } = await client.complete({
58-
// Inherit parent model by default. If parent is on a free model, subagent stays free.
59-
// User can explicitly pass `model` to override.
60-
model: model || registeredParentModel || 'nvidia/nemotron-ultra-253b',
76+
model: subModel,
6177
messages: history,
6278
system: systemPrompt,
6379
tools: toolDefs,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@blockrun/franklin",
3-
"version": "3.6.14",
3+
"version": "3.6.15",
44
"description": "Franklin — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
55
"type": "module",
66
"exports": {

src/commands/start.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -84,22 +84,12 @@ export async function startCommand(options: StartOptions) {
8484

8585
const workDir = process.cwd();
8686

87-
// Show session info immediately, fetch balance in background
88-
// Model is shown in the live status bar — no static line needed.
89-
console.log(chalk.dim(` Wallet: ${walletAddress || 'not set'}`));
90-
console.log(chalk.dim(` Dir: ${workDir}`));
91-
// First-run tip: show if no config file exists yet
92-
if (!configModel && !options.model) {
93-
console.log(chalk.dim(`\n Tip: /model to switch models · /compact to save tokens · /help for all commands`));
94-
}
95-
// Welcome message — show things Hermes/OpenClaw can't do.
96-
// Only on first run or when no model is configured (new user indicator).
97-
// After the user's first session, the tip fades and they go straight to the prompt.
98-
console.log('');
99-
console.log(chalk.dim(' Try something only Franklin can do:'));
100-
console.log(chalk.dim(' ') + chalk.hex('#FFD700')('"what\'s BTC looking like today?"') + chalk.dim(' ← live market data'));
101-
console.log(chalk.dim(' ') + chalk.hex('#60A5FA')('"generate a logo for my startup"') + chalk.dim(' ← AI image gen'));
102-
console.log(chalk.dim(' Code with 55+ models. No API keys. Pay per use.'));
87+
// Session info — aligned, minimal. Model + balance live in the input bar below.
88+
const short = (s: string) => s.length > 14 ? s.slice(0, 6) + '...' + s.slice(-4) : s;
89+
console.log(chalk.dim(' Wallet: ') + (walletAddress ? short(walletAddress) : chalk.yellow('not set')));
90+
console.log(chalk.dim(' Dir: ') + workDir);
91+
console.log(chalk.dim(' Dashboard: ') + chalk.cyan('franklin panel') + chalk.dim(' → http://localhost:3100'));
92+
console.log(chalk.dim(' Help: ') + chalk.cyan('/help'));
10393
console.log('');
10494

10595
// Balance fetcher — used at startup and after each turn

src/tools/subagent.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,37 @@ interface SubAgentInput {
2626
model?: string;
2727
}
2828

29+
// Heuristic: which model IDs are free?
30+
function isFreeModel(m: string): boolean {
31+
return m.startsWith('nvidia/') || m === 'blockrun/free' || m === '';
32+
}
33+
2934
async function execute(input: Record<string, unknown>, ctx: ExecutionScope): Promise<CapabilityResult> {
3035
const { prompt, description, model } = input as unknown as SubAgentInput;
3136

3237
if (!prompt) {
3338
return { output: 'Error: prompt is required', isError: true };
3439
}
3540

41+
// Resolve which model the sub-agent will actually run on
42+
const subModel = model || registeredParentModel || 'nvidia/nemotron-ultra-253b';
43+
44+
// Cost gate: if parent is free but sub-agent wants paid, ask user first.
45+
// Prevents silent charges when the agent decides to spawn a more capable sub-agent.
46+
if (isFreeModel(registeredParentModel) && !isFreeModel(subModel) && ctx.onAskUser) {
47+
const shortLabel = subModel.split('/').pop() || subModel;
48+
const answer = await ctx.onAskUser(
49+
`Sub-agent wants to use ${shortLabel} (paid). Approve?`,
50+
['y', 'n']
51+
);
52+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
53+
return {
54+
output: `Sub-agent skipped — user declined paid model (${shortLabel}). Retry with a free model like nemotron.`,
55+
isError: true,
56+
};
57+
}
58+
}
59+
3660
const client = new ModelClient({
3761
apiUrl: registeredApiUrl,
3862
chain: registeredChain,
@@ -84,9 +108,7 @@ async function execute(input: Record<string, unknown>, ctx: ExecutionScope): Pro
84108

85109
const { content: parts } = await client.complete(
86110
{
87-
// Inherit parent model by default. If parent is on a free model, subagent stays free.
88-
// User can explicitly pass `model` to override.
89-
model: model || registeredParentModel || 'nvidia/nemotron-ultra-253b',
111+
model: subModel,
90112
messages: history,
91113
system: systemPrompt,
92114
tools: toolDefs,

0 commit comments

Comments
 (0)