@blockrun/franklin 3.6.9 → 3.6.10

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.
@@ -627,7 +627,7 @@ export async function handleSlashCommand(input, ctx) {
627
627
  const newModel = resolveModel(input.slice(7).trim());
628
628
  ctx.config.model = newModel;
629
629
  ctx.config.baseModel = newModel; // Update recovery target so loop doesn't reset
630
- ctx.config.onModelChange?.(newModel);
630
+ ctx.config.onModelChange?.(newModel, 'user');
631
631
  ctx.onEvent({ kind: 'text_delta', text: `Model → **${newModel}**\n` });
632
632
  }
633
633
  emitDone(ctx);
@@ -298,7 +298,7 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
298
298
  const baseModel = config.baseModel ?? config.model;
299
299
  if (config.model !== baseModel && !paymentFailedModels.has(baseModel)) {
300
300
  config.model = baseModel;
301
- config.onModelChange?.(baseModel);
301
+ config.onModelChange?.(baseModel, 'system');
302
302
  }
303
303
  turnFailedModels = new Set(); // Fresh slate for transient failures this turn
304
304
  const abort = new AbortController();
@@ -589,7 +589,7 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
589
589
  if (nextFree) {
590
590
  const oldModel = config.model;
591
591
  config.model = nextFree;
592
- config.onModelChange?.(nextFree);
592
+ config.onModelChange?.(nextFree, 'system');
593
593
  onEvent({ kind: 'text_delta', text: `\n*${oldModel} failed — switching to ${nextFree}*\n` });
594
594
  continue; // Retry with next model
595
595
  }
@@ -140,8 +140,10 @@ export interface AgentConfig {
140
140
  permissionPromptFn?: (toolName: string, description: string) => Promise<'yes' | 'no' | 'always'>;
141
141
  /** Routes AskUser questions through ink UI input to avoid raw-mode stdin conflict */
142
142
  onAskUser?: (question: string, options?: string[]) => Promise<string>;
143
- /** Notify UI when agent switches model (e.g. payment fallback) */
144
- onModelChange?: (model: string) => void;
143
+ /** Notify UI when agent switches model. `reason` is 'user' for explicit /model
144
+ * commands and 'system' for payment fallbacks or recovery. User-initiated
145
+ * changes must also update `baseModel`. */
146
+ onModelChange?: (model: string, reason?: 'user' | 'system') => void;
145
147
  /** The user's intended model — updated by /model command, used for turn recovery */
146
148
  baseModel?: string;
147
149
  }
@@ -184,8 +184,13 @@ async function runWithInkUI(agentConfig, model, workDir, version, walletInfo, on
184
184
  walletAddress: walletInfo?.address,
185
185
  walletBalance: walletInfo?.balance,
186
186
  chain: walletInfo?.chain,
187
- onModelChange: (newModel) => {
187
+ onModelChange: (newModel, reason) => {
188
188
  agentConfig.model = newModel;
189
+ // User-initiated switch must also update baseModel so the agent loop
190
+ // doesn't revert to the previous model on the next turn.
191
+ if (reason === 'user') {
192
+ agentConfig.baseModel = newModel;
193
+ }
189
194
  },
190
195
  });
191
196
  // Wire permission prompts through Ink UI to avoid stdin/readline conflict.
@@ -329,12 +334,14 @@ async function handleSlashCommand(cmd, config, ui) {
329
334
  const newModel = parts[1];
330
335
  if (newModel) {
331
336
  config.model = resolveModel(newModel);
337
+ config.baseModel = config.model;
332
338
  console.error(chalk.green(` Model → ${config.model}`));
333
339
  return null;
334
340
  }
335
341
  const picked = await pickModel(config.model);
336
342
  if (picked) {
337
343
  config.model = picked;
344
+ config.baseModel = picked;
338
345
  console.error(chalk.green(` Model → ${config.model}`));
339
346
  }
340
347
  return null;
@@ -343,6 +350,7 @@ async function handleSlashCommand(cmd, config, ui) {
343
350
  const picked = await pickModel(config.model);
344
351
  if (picked) {
345
352
  config.model = picked;
353
+ config.baseModel = picked;
346
354
  console.error(chalk.green(` Model → ${config.model}`));
347
355
  }
348
356
  return null;
package/dist/ui/app.d.ts CHANGED
@@ -22,5 +22,5 @@ export declare function launchInkUI(opts: {
22
22
  walletBalance?: string;
23
23
  chain?: string;
24
24
  showPicker?: boolean;
25
- onModelChange?: (model: string) => void;
25
+ onModelChange?: (model: string, reason?: 'user' | 'system') => void;
26
26
  }): InkUIHandle;
package/dist/ui/app.js CHANGED
@@ -219,7 +219,7 @@ function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain
219
219
  else if (key.return) {
220
220
  const selected = PICKER_MODELS_FLAT[pickerIdx];
221
221
  setCurrentModel(selected.id);
222
- onModelChange(selected.id);
222
+ onModelChange(selected.id, 'user');
223
223
  showStatus(`Model → ${selected.label}`, 'success', 3000);
224
224
  setMode('input');
225
225
  setReady(true);
@@ -290,7 +290,7 @@ function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain
290
290
  if (parts[1]) {
291
291
  const resolved = resolveModel(parts[1]);
292
292
  setCurrentModel(resolved);
293
- onModelChange(resolved);
293
+ onModelChange(resolved, 'user');
294
294
  showStatus(`Model → ${resolved}`, 'success', 3000);
295
295
  }
296
296
  else {
@@ -685,7 +685,7 @@ export function launchInkUI(opts) {
685
685
  // Agent loop hasn't called waitForInput yet — queue the input
686
686
  pendingInput = value;
687
687
  }
688
- }, onModelChange: (model) => { opts.onModelChange?.(model); }, onAbort: () => { abortCallback?.(); }, onExit: () => {
688
+ }, onModelChange: (model, reason) => { opts.onModelChange?.(model, reason); }, onAbort: () => { abortCallback?.(); }, onExit: () => {
689
689
  exiting = true;
690
690
  if (resolveInput) {
691
691
  resolveInput(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.6.9",
3
+ "version": "3.6.10",
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": {