@blockrun/franklin 3.7.5 → 3.7.6

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.
@@ -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
- // Gate 1: only COMPLEX or REASONING tiers benefit from planning
21
- if (tier !== 'COMPLEX' && tier !== 'REASONING')
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
- // Gate 4: ultrathink already provides deep reasoning
23
+ // Ultrathink already provides deep reasoning
30
24
  if (ultrathink)
31
25
  return false;
32
- // Gate 5: user disabled planning for this session
33
- if (planDisabled)
26
+ // Only auto or premium profiles (eco/free are cost-constrained)
27
+ if (profile !== 'auto' && profile !== 'premium')
34
28
  return false;
35
- // Gate 6: must have agentic or multi-step signals
36
- const hasAgenticKeyword = AGENTIC_KEYWORDS.test(userText);
37
- const hasMultiStep = MULTI_STEP_PATTERN.test(userText);
38
- return hasAgenticKeyword || hasMultiStep;
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
  /**
@@ -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;
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.7.5",
3
+ "version": "3.7.6",
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": {