@blockrun/franklin 3.15.49 → 3.15.50

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/ui/app.js CHANGED
@@ -14,6 +14,7 @@ import { resolveModel, PICKER_CATEGORIES, PICKER_MODELS_FLAT, } from './model-pi
14
14
  import { estimateCost } from '../pricing.js';
15
15
  import { formatTokens, shortModelName } from '../stats/format.js';
16
16
  import { mouse, forceDisableMouseTracking } from './mouse.js';
17
+ import { resolveAskUserAnswer } from './ask-user-answer.js';
17
18
  // ─── Full-width input box ──────────────────────────────────────────────────
18
19
  const DISABLE_AUTO_WRAP = '\x1b[?7l';
19
20
  const ENABLE_AUTO_WRAP = '\x1b[?7h';
@@ -827,7 +828,12 @@ function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain
827
828
  ? _jsxs(Text, { color: r.ctxPct >= 80 ? 'red' : r.ctxPct >= 50 ? 'yellow' : undefined, dimColor: r.ctxPct < 50, children: [" \u00B7 ctx ", r.ctxPct, "%"] })
828
829
  : ''] }) }))] }, r.key));
829
830
  } }), permissionRequest && (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 2, children: [_jsx(Text, { color: "red", bold: true, children: "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 \u26A0 ACTION REQUIRED \u26A0 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501" }), _jsx(Text, { color: "yellow", children: "\u256D\u2500 Permission required \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsxs(Text, { color: "yellow", children: ["\u2502 ", _jsx(Text, { bold: true, children: permissionRequest.toolName })] }), permissionRequest.description.split('\n').map((line, i) => (_jsxs(Text, { dimColor: true, children: ["\u2502 ", line] }, i))), _jsx(Text, { color: "yellow", children: "\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsx(Box, { marginLeft: 2, children: _jsxs(Text, { children: [_jsx(Text, { bold: true, color: "green", children: "[y]" }), _jsx(Text, { dimColor: true, children: " yes " }), _jsx(Text, { bold: true, color: "cyan", children: "[a]" }), _jsx(Text, { dimColor: true, children: " always " }), _jsx(Text, { bold: true, color: "red", children: "[n]" }), _jsx(Text, { dimColor: true, children: " no" })] }) })] })), askUserRequest && (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 2, children: [_jsx(Text, { color: "magenta", bold: true, children: "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 \u26A0 ANSWER REQUIRED \u26A0 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501" }), _jsx(Text, { color: "cyan", children: "\u256D\u2500 Question \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsxs(Text, { color: "cyan", children: ["\u2502 ", _jsx(Text, { bold: true, children: askUserRequest.question })] }), askUserRequest.options && askUserRequest.options.length > 0 && (askUserRequest.options.map((opt, i) => (_jsxs(Text, { dimColor: true, children: ["\u2502 ", i + 1, ". ", opt] }, i)))), _jsx(Text, { color: "cyan", children: "\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { bold: true, children: "answer> " }), _jsx(TextInput, { value: askUserInput, onChange: setAskUserInput, onSubmit: (val) => {
830
- const answer = val.trim() || '(no response)';
831
+ // resolveAskUserAnswer translates "1" / "2" / ... into the
832
+ // matching label string when the dialog showed a numbered
833
+ // option list. Without it, every onAskUser caller's
834
+ // exact-label match fails for digit answers and silently
835
+ // falls through to the default branch (typically cancel).
836
+ const answer = resolveAskUserAnswer(val, askUserRequest.options);
831
837
  const r = askUserRequest.resolve;
832
838
  setAskUserRequest(null);
833
839
  setAskUserInput('');
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Resolve a user-typed AskUser answer against the option list.
3
+ *
4
+ * The TUI renders option labels as a numbered list ("1. X", "2. Y", …),
5
+ * so users naturally type the digit. Every tool-side onAskUser caller
6
+ * (videogen.ts:113, modal.ts:371, jupiter.ts:368, zerox-base.ts:453,
7
+ * zerox-gasless.ts:446) does an exact-string match against the full
8
+ * label, so a bare "1" silently falls through to the caller's default
9
+ * branch — which is typically "cancel".
10
+ *
11
+ * Verified 2026-05-04 in a live session: user typed "1" twice in a
12
+ * VideoGen flow, both invocations returned "Video generation cancelled
13
+ * (No USDC was spent)" even though the wallet had $94.72 and the
14
+ * Content budget had $2.00 untouched.
15
+ *
16
+ * Translation rules:
17
+ * - "" → "(no response)" (preserve the existing empty-input fallback)
18
+ * - "<digit>" with options.length > 0 and 1 ≤ digit ≤ options.length
19
+ * → the matching label string
20
+ * - anything else → the trimmed input verbatim (callers that match
21
+ * against label can still get a literal answer when the user types
22
+ * it out, and free-form text questions still work the same way)
23
+ */
24
+ export declare function resolveAskUserAnswer(raw: string, options: readonly string[] | undefined): string;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Resolve a user-typed AskUser answer against the option list.
3
+ *
4
+ * The TUI renders option labels as a numbered list ("1. X", "2. Y", …),
5
+ * so users naturally type the digit. Every tool-side onAskUser caller
6
+ * (videogen.ts:113, modal.ts:371, jupiter.ts:368, zerox-base.ts:453,
7
+ * zerox-gasless.ts:446) does an exact-string match against the full
8
+ * label, so a bare "1" silently falls through to the caller's default
9
+ * branch — which is typically "cancel".
10
+ *
11
+ * Verified 2026-05-04 in a live session: user typed "1" twice in a
12
+ * VideoGen flow, both invocations returned "Video generation cancelled
13
+ * (No USDC was spent)" even though the wallet had $94.72 and the
14
+ * Content budget had $2.00 untouched.
15
+ *
16
+ * Translation rules:
17
+ * - "" → "(no response)" (preserve the existing empty-input fallback)
18
+ * - "<digit>" with options.length > 0 and 1 ≤ digit ≤ options.length
19
+ * → the matching label string
20
+ * - anything else → the trimmed input verbatim (callers that match
21
+ * against label can still get a literal answer when the user types
22
+ * it out, and free-form text questions still work the same way)
23
+ */
24
+ export function resolveAskUserAnswer(raw, options) {
25
+ const trimmed = raw.trim();
26
+ if (!trimmed)
27
+ return '(no response)';
28
+ if (options && options.length > 0 && /^\d+$/.test(trimmed)) {
29
+ const idx = parseInt(trimmed, 10) - 1;
30
+ if (idx >= 0 && idx < options.length)
31
+ return options[idx];
32
+ }
33
+ return trimmed;
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.49",
3
+ "version": "3.15.50",
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": {