@blockrun/franklin 3.7.5 → 3.7.7
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/agent/optimize.js +1 -0
- package/dist/agent/planner.js +19 -15
- package/dist/agent/tokens.js +1 -0
- package/dist/agent/tool-guard.d.ts +3 -0
- package/dist/agent/tool-guard.js +40 -0
- package/dist/pricing.js +2 -1
- package/dist/router/index.js +4 -4
- package/dist/ui/app.js +24 -4
- package/dist/ui/model-picker.js +5 -2
- package/package.json +1 -1
package/dist/agent/optimize.js
CHANGED
|
@@ -21,6 +21,7 @@ export const CAPPED_MAX_TOKENS = 16_384;
|
|
|
21
21
|
export const ESCALATED_MAX_TOKENS = 65_536;
|
|
22
22
|
/** Per-model max output tokens — prevents requesting more than the model supports */
|
|
23
23
|
const MODEL_MAX_OUTPUT = {
|
|
24
|
+
'anthropic/claude-opus-4.7': 128_000,
|
|
24
25
|
'anthropic/claude-opus-4.6': 32_000,
|
|
25
26
|
'anthropic/claude-sonnet-4.6': 64_000,
|
|
26
27
|
'anthropic/claude-haiku-4.5-20251001': 16_384,
|
package/dist/agent/planner.js
CHANGED
|
@@ -17,25 +17,29 @@ const MULTI_STEP_PATTERN = /first.*then|step\s+\d|\d+\.\s|and\s+then|after\s+tha
|
|
|
17
17
|
* the overhead of an extra planning call.
|
|
18
18
|
*/
|
|
19
19
|
export function shouldPlan(tier, profile, userText, ultrathink, planDisabled) {
|
|
20
|
-
//
|
|
21
|
-
if (
|
|
22
|
-
return false;
|
|
23
|
-
// Gate 2: only auto or premium profiles (eco/free already cost-optimized)
|
|
24
|
-
if (profile !== 'auto' && profile !== 'premium')
|
|
25
|
-
return false;
|
|
26
|
-
// Gate 3: skip short queries — planning overhead not worth it
|
|
27
|
-
if (userText.length < 80)
|
|
20
|
+
// User disabled planning for this session
|
|
21
|
+
if (planDisabled)
|
|
28
22
|
return false;
|
|
29
|
-
//
|
|
23
|
+
// Ultrathink already provides deep reasoning
|
|
30
24
|
if (ultrathink)
|
|
31
25
|
return false;
|
|
32
|
-
//
|
|
33
|
-
if (
|
|
26
|
+
// Only auto or premium profiles (eco/free are cost-constrained)
|
|
27
|
+
if (profile !== 'auto' && profile !== 'premium')
|
|
34
28
|
return false;
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
// Explicit multi-step language always plans, regardless of tier / length
|
|
30
|
+
// ("first ... then ...", "step 1 ... step 2 ...", numbered lists, etc.)
|
|
31
|
+
if (MULTI_STEP_PATTERN.test(userText))
|
|
32
|
+
return true;
|
|
33
|
+
// Planning is high-ROI on COMPLEX / REASONING tiers for agentic verbs,
|
|
34
|
+
// even when the prompt is short ("refactor the wallet module", "migrate to TS")
|
|
35
|
+
if (tier === 'COMPLEX' || tier === 'REASONING') {
|
|
36
|
+
return AGENTIC_KEYWORDS.test(userText) || userText.length >= 60;
|
|
37
|
+
}
|
|
38
|
+
// On MEDIUM tier: plan only if long AND agentic
|
|
39
|
+
if (tier === 'MEDIUM' && userText.length >= 120 && AGENTIC_KEYWORDS.test(userText)) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
39
43
|
}
|
|
40
44
|
// ─── Planning Prompt ─────────────────────────────────────────────────────
|
|
41
45
|
/**
|
package/dist/agent/tokens.js
CHANGED
|
@@ -151,6 +151,7 @@ export function estimateHistoryTokens(history) {
|
|
|
151
151
|
*/
|
|
152
152
|
const MODEL_CONTEXT_WINDOWS = {
|
|
153
153
|
// Anthropic
|
|
154
|
+
'anthropic/claude-opus-4.7': 1_000_000,
|
|
154
155
|
'anthropic/claude-opus-4.6': 200_000,
|
|
155
156
|
'anthropic/claude-sonnet-4.6': 200_000,
|
|
156
157
|
'anthropic/claude-sonnet-4': 200_000,
|
|
@@ -16,11 +16,14 @@ export declare class SessionToolGuard {
|
|
|
16
16
|
private toolErrorCounts;
|
|
17
17
|
private recentGreps;
|
|
18
18
|
private recentGlobs;
|
|
19
|
+
private recentBash;
|
|
19
20
|
startTurn(): void;
|
|
20
21
|
beforeExecute(invocation: CapabilityInvocation, scope: ExecutionScope): Promise<CapabilityResult | null>;
|
|
22
|
+
private beforeBash;
|
|
21
23
|
private beforeGrep;
|
|
22
24
|
private beforeGlob;
|
|
23
25
|
afterExecute(invocation: CapabilityInvocation, result: CapabilityResult): void;
|
|
26
|
+
private afterBash;
|
|
24
27
|
private afterGrep;
|
|
25
28
|
private afterGlob;
|
|
26
29
|
cancelInvocation(invocationId: string): void;
|
package/dist/agent/tool-guard.js
CHANGED
|
@@ -94,6 +94,7 @@ export class SessionToolGuard {
|
|
|
94
94
|
// five times in a row when they're confused. Tell them once that it already failed.
|
|
95
95
|
recentGreps = new Map();
|
|
96
96
|
recentGlobs = new Map();
|
|
97
|
+
recentBash = new Map();
|
|
97
98
|
startTurn() {
|
|
98
99
|
this.turn++;
|
|
99
100
|
this.webSearchesThisTurn = 0;
|
|
@@ -123,10 +124,33 @@ export class SessionToolGuard {
|
|
|
123
124
|
return this.beforeGrep(invocation);
|
|
124
125
|
case 'Glob':
|
|
125
126
|
return this.beforeGlob(invocation);
|
|
127
|
+
case 'Bash':
|
|
128
|
+
return this.beforeBash(invocation);
|
|
126
129
|
default:
|
|
127
130
|
return null;
|
|
128
131
|
}
|
|
129
132
|
}
|
|
133
|
+
beforeBash(invocation) {
|
|
134
|
+
const cmd = String(invocation.input.command ?? '').trim();
|
|
135
|
+
if (!cmd)
|
|
136
|
+
return null;
|
|
137
|
+
// Only dedup deterministic read-only commands. Skip anything writing/network/long-running.
|
|
138
|
+
const writeKeywords = /\b(rm|mv|cp|mkdir|touch|chmod|chown|write|install|build|publish|push|pull|curl|wget|fetch|npm|pnpm|yarn|pip|cargo|go\s+(build|run|test)|docker|kubectl|tar|zip|unzip|tee|>\s|>>\s)\b/;
|
|
139
|
+
if (writeKeywords.test(cmd))
|
|
140
|
+
return null;
|
|
141
|
+
const key = cmd;
|
|
142
|
+
const cached = this.recentBash.get(key);
|
|
143
|
+
if (cached) {
|
|
144
|
+
const lead = cached.isError
|
|
145
|
+
? 'That exact Bash command was already run this session and FAILED:'
|
|
146
|
+
: 'That exact Bash command was already run this session and returned:';
|
|
147
|
+
return {
|
|
148
|
+
output: `${lead}\n${cached.preview}\n\n` +
|
|
149
|
+
'Do not re-run the same command. If the output was insufficient, run a different command or use a dedicated tool (Read for files, Grep/Glob for searching).',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
130
154
|
beforeGrep(invocation) {
|
|
131
155
|
const pattern = String(invocation.input.pattern ?? '').trim();
|
|
132
156
|
const path = String(invocation.input.path ?? '').trim();
|
|
@@ -181,10 +205,26 @@ export class SessionToolGuard {
|
|
|
181
205
|
case 'Glob':
|
|
182
206
|
this.afterGlob(invocation, result);
|
|
183
207
|
break;
|
|
208
|
+
case 'Bash':
|
|
209
|
+
this.afterBash(invocation, result);
|
|
210
|
+
break;
|
|
184
211
|
default:
|
|
185
212
|
break;
|
|
186
213
|
}
|
|
187
214
|
}
|
|
215
|
+
afterBash(invocation, result) {
|
|
216
|
+
const cmd = String(invocation.input.command ?? '').trim();
|
|
217
|
+
if (!cmd)
|
|
218
|
+
return;
|
|
219
|
+
const writeKeywords = /\b(rm|mv|cp|mkdir|touch|chmod|chown|write|install|build|publish|push|pull|curl|wget|fetch|npm|pnpm|yarn|pip|cargo|go\s+(build|run|test)|docker|kubectl|tar|zip|unzip|tee|>\s|>>\s)\b/;
|
|
220
|
+
if (writeKeywords.test(cmd))
|
|
221
|
+
return;
|
|
222
|
+
const output = String(result.output ?? '');
|
|
223
|
+
const preview = output.length > MAX_PREVIEW_CHARS
|
|
224
|
+
? output.slice(0, MAX_PREVIEW_CHARS) + '…'
|
|
225
|
+
: output;
|
|
226
|
+
this.recentBash.set(cmd, { preview, turn: this.turn, isError: !!result.isError });
|
|
227
|
+
}
|
|
188
228
|
afterGrep(invocation, result) {
|
|
189
229
|
const pattern = String(invocation.input.pattern ?? '').trim();
|
|
190
230
|
const path = String(invocation.input.path ?? '').trim();
|
package/dist/pricing.js
CHANGED
|
@@ -23,6 +23,7 @@ export const MODEL_PRICING = {
|
|
|
23
23
|
// Anthropic
|
|
24
24
|
'anthropic/claude-sonnet-4.6': { input: 3.0, output: 15.0 },
|
|
25
25
|
'anthropic/claude-opus-4.6': { input: 5.0, output: 25.0 },
|
|
26
|
+
'anthropic/claude-opus-4.7': { input: 5.0, output: 25.0 },
|
|
26
27
|
'anthropic/claude-haiku-4.5': { input: 1.0, output: 5.0 },
|
|
27
28
|
'anthropic/claude-haiku-4.5-20251001': { input: 1.0, output: 5.0 },
|
|
28
29
|
// OpenAI
|
|
@@ -76,7 +77,7 @@ export const MODEL_PRICING = {
|
|
|
76
77
|
'zai/glm-5.1-turbo': { input: 0, output: 0, perCall: 0.001 }, // client alias for zai/glm-5-turbo
|
|
77
78
|
};
|
|
78
79
|
/** Opus pricing for savings calculations */
|
|
79
|
-
export const OPUS_PRICING = MODEL_PRICING['anthropic/claude-opus-4.
|
|
80
|
+
export const OPUS_PRICING = MODEL_PRICING['anthropic/claude-opus-4.7'];
|
|
80
81
|
/**
|
|
81
82
|
* Estimate cost in USD for a request.
|
|
82
83
|
* Falls back to $2/$10 per 1M for unknown models.
|
package/dist/router/index.js
CHANGED
|
@@ -46,10 +46,10 @@ const AUTO_TIERS = {
|
|
|
46
46
|
},
|
|
47
47
|
COMPLEX: {
|
|
48
48
|
primary: 'anthropic/claude-sonnet-4.6',
|
|
49
|
-
fallback: ['openai/gpt-5.4', 'anthropic/claude-opus-4.
|
|
49
|
+
fallback: ['openai/gpt-5.4', 'anthropic/claude-opus-4.7'],
|
|
50
50
|
},
|
|
51
51
|
REASONING: {
|
|
52
|
-
primary: 'anthropic/claude-opus-4.
|
|
52
|
+
primary: 'anthropic/claude-opus-4.7',
|
|
53
53
|
fallback: ['openai/o3', 'xai/grok-4-1-fast-reasoning'],
|
|
54
54
|
},
|
|
55
55
|
};
|
|
@@ -81,12 +81,12 @@ const PREMIUM_TIERS = {
|
|
|
81
81
|
fallback: ['anthropic/claude-sonnet-4.6'],
|
|
82
82
|
},
|
|
83
83
|
COMPLEX: {
|
|
84
|
-
primary: 'anthropic/claude-opus-4.
|
|
84
|
+
primary: 'anthropic/claude-opus-4.7',
|
|
85
85
|
fallback: ['openai/gpt-5.4', 'anthropic/claude-sonnet-4.6'],
|
|
86
86
|
},
|
|
87
87
|
REASONING: {
|
|
88
88
|
primary: 'anthropic/claude-sonnet-4.6',
|
|
89
|
-
fallback: ['anthropic/claude-opus-4.
|
|
89
|
+
fallback: ['anthropic/claude-opus-4.7', 'openai/o3'],
|
|
90
90
|
},
|
|
91
91
|
};
|
|
92
92
|
// ─── Keywords for Classification ───
|
package/dist/ui/app.js
CHANGED
|
@@ -15,16 +15,36 @@ import { estimateCost } from '../pricing.js';
|
|
|
15
15
|
import { formatTokens, shortModelName } from '../stats/format.js';
|
|
16
16
|
import { mouse, forceDisableMouseTracking } from './mouse.js';
|
|
17
17
|
// ─── Full-width input box ──────────────────────────────────────────────────
|
|
18
|
-
|
|
18
|
+
// Subscribe to terminal resize so React re-renders with fresh dimensions.
|
|
19
|
+
// Without this, useStdout() returns a stable ref and children that read
|
|
20
|
+
// stdout.columns on each render still need React to re-execute them — which
|
|
21
|
+
// only happens if some state changes. stdout.on('resize') → setState does that.
|
|
22
|
+
function useTerminalSize() {
|
|
19
23
|
const { stdout } = useStdout();
|
|
20
|
-
const
|
|
21
|
-
|
|
24
|
+
const [size, setSize] = useState(() => ({
|
|
25
|
+
cols: stdout?.columns ?? 80,
|
|
26
|
+
rows: stdout?.rows ?? 24,
|
|
27
|
+
}));
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!stdout)
|
|
30
|
+
return;
|
|
31
|
+
const onResize = () => setSize({
|
|
32
|
+
cols: stdout.columns ?? 80,
|
|
33
|
+
rows: stdout.rows ?? 24,
|
|
34
|
+
});
|
|
35
|
+
stdout.on('resize', onResize);
|
|
36
|
+
return () => { stdout.off('resize', onResize); };
|
|
37
|
+
}, [stdout]);
|
|
38
|
+
return size;
|
|
39
|
+
}
|
|
40
|
+
function InputBox({ input, setInput, onSubmit, model, balance, sessionCost, queued, queuedCount, focused, busy, contextPct, vimMode, onVimModeChange }) {
|
|
41
|
+
const { cols } = useTerminalSize();
|
|
22
42
|
const placeholder = busy
|
|
23
43
|
? (queued
|
|
24
44
|
? `⏎ ${queuedCount ?? 1} queued: ${queued.slice(0, 40)}`
|
|
25
45
|
: 'Working...')
|
|
26
46
|
: 'Type a message...';
|
|
27
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
|
|
47
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { borderStyle: "round", borderDimColor: true, paddingX: 1, width: cols, children: [busy && !input ? _jsxs(Text, { color: "yellow", children: [_jsx(Spinner, { type: "dots" }), " "] }) : null, _jsx(Box, { flexGrow: 1, children: vimMode ? (_jsx(VimInput, { value: input, onChange: setInput, onSubmit: onSubmit, placeholder: placeholder, focus: focused !== false, showMode: true, onModeChange: onVimModeChange })) : (_jsx(TextInput, { value: input, onChange: setInput, onSubmit: onSubmit, placeholder: placeholder, focus: focused !== false })) })] }), _jsx(Box, { marginLeft: 1, children: _jsxs(Text, { dimColor: true, children: [busy ? _jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }) : null, busy ? ' ' : '', shortModelName(model), " \u00B7 ", balance, sessionCost > 0.00001 ? _jsxs(Text, { color: "yellow", children: [" -$", sessionCost.toFixed(4)] }) : '', contextPct !== undefined && contextPct > 0 ? (() => {
|
|
28
48
|
// Visual context bar: ▓▓▓▓▓▓░░░░ 75%
|
|
29
49
|
const filled = Math.round(contextPct / 10);
|
|
30
50
|
const empty = 10 - filled;
|
package/dist/ui/model-picker.js
CHANGED
|
@@ -14,7 +14,9 @@ export const MODEL_SHORTCUTS = {
|
|
|
14
14
|
// Anthropic
|
|
15
15
|
sonnet: 'anthropic/claude-sonnet-4.6',
|
|
16
16
|
claude: 'anthropic/claude-sonnet-4.6',
|
|
17
|
-
opus: 'anthropic/claude-opus-4.
|
|
17
|
+
opus: 'anthropic/claude-opus-4.7',
|
|
18
|
+
'opus-4.7': 'anthropic/claude-opus-4.7',
|
|
19
|
+
'opus-4.6': 'anthropic/claude-opus-4.6',
|
|
18
20
|
haiku: 'anthropic/claude-haiku-4.5-20251001',
|
|
19
21
|
// OpenAI
|
|
20
22
|
gpt: 'openai/gpt-5.4',
|
|
@@ -93,8 +95,9 @@ export const PICKER_CATEGORIES = [
|
|
|
93
95
|
{
|
|
94
96
|
category: '✨ Premium frontier',
|
|
95
97
|
models: [
|
|
98
|
+
{ id: 'anthropic/claude-opus-4.7', shortcut: 'opus', label: 'Claude Opus 4.7', price: '$5/$25', highlight: true },
|
|
96
99
|
{ id: 'anthropic/claude-sonnet-4.6', shortcut: 'sonnet', label: 'Claude Sonnet 4.6', price: '$3/$15' },
|
|
97
|
-
{ id: 'anthropic/claude-opus-4.6', shortcut: 'opus', label: 'Claude Opus 4.6', price: '$5/$25' },
|
|
100
|
+
{ id: 'anthropic/claude-opus-4.6', shortcut: 'opus-4.6', label: 'Claude Opus 4.6', price: '$5/$25' },
|
|
98
101
|
{ id: 'openai/gpt-5.4', shortcut: 'gpt', label: 'GPT-5.4', price: '$2.5/$15' },
|
|
99
102
|
{ id: 'openai/gpt-5.4-pro', shortcut: 'gpt-5.4-pro', label: 'GPT-5.4 Pro', price: '$30/$180' },
|
|
100
103
|
{ id: 'google/gemini-2.5-pro', shortcut: 'gemini', label: 'Gemini 2.5 Pro', price: '$1.25/$10' },
|
package/package.json
CHANGED