@blockrun/franklin 3.15.86 → 3.15.88
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/context.js +4 -4
- package/dist/agent/evaluator.js +1 -1
- package/dist/agent/loop.js +12 -9
- package/dist/agent/media-router.js +3 -3
- package/dist/agent/turn-analyzer.js +7 -7
- package/dist/commands/content.d.ts +3 -3
- package/dist/commands/content.js +3 -3
- package/dist/commands/start.js +18 -18
- package/dist/router/categories.js +4 -6
- package/dist/router/index.js +10 -8
- package/dist/social/a11y.d.ts +1 -1
- package/dist/social/a11y.js +6 -2
- package/dist/tools/prediction.js +1 -1
- package/dist/tools/searchx.js +3 -3
- package/dist/tools/wallet.js +1 -1
- package/package.json +1 -1
package/dist/agent/context.js
CHANGED
|
@@ -18,7 +18,7 @@ You are an interactive agent — not a chatbot. Use the tools available to you t
|
|
|
18
18
|
|
|
19
19
|
# Franklin has hands
|
|
20
20
|
You run with live tools by default:
|
|
21
|
-
- **Wallet** — read your own chain, address, and USDC balance. Use this for any "what's my balance / how much money /
|
|
21
|
+
- **Wallet** — read your own chain, address, and USDC balance. Use this for any "what's my balance / how much money / wallet status" question instead of running \`franklin balance\` via Bash. Free, one call, never costs USDC.
|
|
22
22
|
- **TradingMarket** — current stock / FX / crypto / commodity prices (BlockRun Gateway / Pyth; wallet pays automatically, $0.001/stock call, free for everything else).
|
|
23
23
|
- **ExaAnswer / ExaSearch / ExaReadUrls** — cited current-events answers, semantic web search, clean URL content.
|
|
24
24
|
- **WebSearch / WebFetch** — live web.
|
|
@@ -88,7 +88,7 @@ function getOutputEfficiencySection() {
|
|
|
88
88
|
return `# Output Efficiency
|
|
89
89
|
Go straight to the point. Lead with the action, not the reasoning. Do not restate what the user said.
|
|
90
90
|
|
|
91
|
-
**No pre-tool narration.** Do NOT write things like "
|
|
91
|
+
**No pre-tool narration.** Do NOT write things like "Let me read the file...", "I'll now search for...", "Let me investigate...", "Now I'm going to X", "OK now I have everything I need", "Perfect!", "Got it, now I fully understand". These phrases are internal monologue — the user can see your tool calls directly and does not need step-by-step play-by-play. Just call the tool. The same rule applies in any language — no equivalent narration in non-English replies either.
|
|
92
92
|
|
|
93
93
|
The exception: a single short sentence between tool calls is fine when it tells the user something they would otherwise miss — a finding ("Build passes — moving on to tests."), a course correction ("That approach won't work — switching to X."), or a one-line status before a long-running operation. One sentence per update is enough.
|
|
94
94
|
|
|
@@ -342,7 +342,7 @@ If you find yourself about to emit one of these, stop and call the tool instead.
|
|
|
342
342
|
- "what are the odds on Polymarket / Kalshi specifically" → \`searchPolymarket\` (\$0.001) and \`searchKalshi\` (\$0.001) **in parallel**; comparing implied probability across the two venues is the high-value answer.
|
|
343
343
|
- "where do Polymarket and Kalshi disagree / arbitrage" → \`crossPlatform\` (\$0.005) returns pre-matched pairs.
|
|
344
344
|
- "who's profitable / top traders / who should I follow on Polymarket" → \`leaderboard\` (\$0.001) — global top wallets by P&L.
|
|
345
|
-
- "analyze this wallet / can I copy this trader /
|
|
345
|
+
- "analyze this wallet / can I copy this trader / show me their P&L AND positions" → run \`walletProfile\` + \`walletPnl\` + \`walletPositions\` IN PARALLEL with the same address. Three \$0.005 calls = full picture for \$0.015. Do NOT \`Bash\`-curl \`data-api.polymarket.com\` directly — those are paid Predexon endpoints and going around them defeats the wallet-attached architecture. If just the profile is needed: \`walletProfile\` alone (single address → /wallet/{addr}, comma-list → batch).
|
|
346
346
|
- "what are smart traders betting on right now / smart money flow across markets" → \`smartActivity\` (\$0.005) — markets where high-P&L wallets are positioning.
|
|
347
347
|
- "show smart money on this specific Polymarket market / this condition_id" → \`smartMoney\` (\$0.005) with \`conditionId="<condition_id>"\`.
|
|
348
348
|
|
|
@@ -352,7 +352,7 @@ NEVER answer "what are the odds of X" from training-data memory — these are li
|
|
|
352
352
|
- Run **TradingSignal** with default lookback (90d). Lower values leave MACD undefined.
|
|
353
353
|
- The tool returns a **Verdict** section with \`Direction\`, \`Bull signals\`, \`Bear signals\`. Echo it directly. Do not soften "bullish" to "leaning slightly positive" — say what the data says.
|
|
354
354
|
- If \`Data Notes\` lists an indicator as "insufficient data", state that explicitly to the user and suggest re-running with more days. Do NOT pretend that indicator is "neutral".
|
|
355
|
-
- **Forbidden default**: "
|
|
355
|
+
- **Forbidden default**: "wait and see" / "hold for clearer signals" / equivalent hedging in any language — these are bugs when ≥2 indicators voted in a clear direction. Bail out to that posture ONLY when (a) the Verdict says \`neutral\` AND (b) the bull/bear signal lists are both genuinely empty or one of each. Otherwise commit to a direction with the reasoning the tool already gave you.
|
|
356
356
|
|
|
357
357
|
**Media generation (ImageGen / VideoGen).** Pass just the user's descriptive prompt and the output path — do NOT pass \`model\`. The harness picks the right model for the requested style + budget, refines loose prompts using a 5-slot template (scene / subject / details / use case / constraints), and surfaces both the refinement and a cost proposal through AskUser before spending. If the user wants their prompt left exactly as written, prefix it with \`///\` to skip refinement. Only pass \`model\` explicitly if the user named one specifically.`;
|
|
358
358
|
}
|
package/dist/agent/evaluator.js
CHANGED
|
@@ -48,7 +48,7 @@ Flag as ungrounded:
|
|
|
48
48
|
- Invented specifics — names, numbers, dates the model produced without a tool call supporting them
|
|
49
49
|
|
|
50
50
|
### B. Tool-use refusal (NEW)
|
|
51
|
-
If the user clearly asked for live-world data — a current price, today's news, the latest state of X — and the assistant's answer contains a refusal or deflection (e.g. "I can't provide real-time prices", "
|
|
51
|
+
If the user clearly asked for live-world data — a current price, today's news, the latest state of X — and the assistant's answer contains a refusal or deflection (e.g. "I can't provide real-time prices", "I don't have access to live data", "check Yahoo Finance yourself", "as an AI I cannot fetch this"), that is also UNGROUNDED. The same rule applies in any language. Franklin HAS tools for this (TradingMarket for prices, ExaAnswer for current events, WebSearch for general web, etc.). Refusing to reach for them is the failure this check was built for.
|
|
52
52
|
|
|
53
53
|
Flag as tool-use refusal:
|
|
54
54
|
- "I can't check real-time prices"
|
package/dist/agent/loop.js
CHANGED
|
@@ -291,10 +291,10 @@ export function looksLikeGatewayErrorAsText(parts) {
|
|
|
291
291
|
* pinned by tool_choice when the user prompt actually references that
|
|
292
292
|
* tool's domain — otherwise we let the smart generator pick from any tool.
|
|
293
293
|
*
|
|
294
|
-
* The motivating bug: a real-estate question ("
|
|
295
|
-
* answer flagged as ungrounded for citing $/sqft figures. The
|
|
296
|
-
* evaluator model picked TradingMarket as the missing tool because
|
|
297
|
-
* was the first example in the evaluator prompt. Forcing TradingMarket
|
|
294
|
+
* The motivating bug: a real-estate question ("can I negotiate 20% off")
|
|
295
|
+
* had its answer flagged as ungrounded for citing $/sqft figures. The
|
|
296
|
+
* cheap evaluator model picked TradingMarket as the missing tool because
|
|
297
|
+
* it was the first example in the evaluator prompt. Forcing TradingMarket
|
|
298
298
|
* (a crypto-only tool) on a housing question made the retry useless.
|
|
299
299
|
*
|
|
300
300
|
* This function returns false for specialized tools when the prompt has
|
|
@@ -304,16 +304,18 @@ export function looksLikeGatewayErrorAsText(parts) {
|
|
|
304
304
|
*/
|
|
305
305
|
function isToolRelevantToPrompt(toolName, promptLower) {
|
|
306
306
|
// Crypto trading tools — need a ticker, "crypto", "coin", "swap", etc.
|
|
307
|
+
// English-only fast path; the LLM-level classifier handles other languages
|
|
308
|
+
// before this domain-relevance check runs.
|
|
307
309
|
if (/^(Trading|DefiLlama|Jupiter|Base0x|Base0xGasless)/i.test(toolName)) {
|
|
308
|
-
return /\b(btc|eth|sol|xrp|doge|usdc|usdt|crypto|coin|token|defi|tvl|yield|swap|jupiter|uniswap|pump\.fun|solana|base chain|polygon|ethereum
|
|
310
|
+
return /\b(btc|eth|sol|xrp|doge|usdc|usdt|crypto|coin|token|defi|tvl|yield|swap|jupiter|uniswap|pump\.fun|solana|base chain|polygon|ethereum)\b/i.test(promptLower);
|
|
309
311
|
}
|
|
310
312
|
// X.com search — need an @handle, "twitter", "tweet", "X.com"
|
|
311
313
|
if (/^SearchX$/i.test(toolName) || /^PostToX$/i.test(toolName)) {
|
|
312
|
-
return /(@\w+|twitter|x\.com|tweet
|
|
314
|
+
return /(@\w+|twitter|x\.com|tweet)/i.test(promptLower);
|
|
313
315
|
}
|
|
314
316
|
// Image / video / music gen — need a creative-content request
|
|
315
317
|
if (/^(ImageGen|VideoGen|MusicGen)$/i.test(toolName)) {
|
|
316
|
-
return /\b(image|picture|photo|video|clip|music|song|generate|create|render|draw
|
|
318
|
+
return /\b(image|picture|photo|video|clip|music|song|generate|create|render|draw)\b/i.test(promptLower);
|
|
317
319
|
}
|
|
318
320
|
// General-purpose / file / shell tools — always relevant.
|
|
319
321
|
return true;
|
|
@@ -860,7 +862,8 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
|
|
|
860
862
|
try {
|
|
861
863
|
// Anchor 1: the user's current message (already in lastUserInput).
|
|
862
864
|
// Anchor 2: first chunk of the previous assistant reply — gives the
|
|
863
|
-
// analyzer enough context to resolve deictic follow-ups like
|
|
865
|
+
// analyzer enough context to resolve deictic follow-ups like
|
|
866
|
+
// "and that one?" / "what about AAPL".
|
|
864
867
|
const lastAssistantText = (() => {
|
|
865
868
|
const prior = [...history.slice(0, -1)].reverse()
|
|
866
869
|
.find((m) => m.role === 'assistant');
|
|
@@ -1843,7 +1846,7 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
|
|
|
1843
1846
|
recordOutcome(lastRoutedCategory, lastRoutedModel, 'continued', turnToolCalls);
|
|
1844
1847
|
}
|
|
1845
1848
|
// End-of-turn marker for question-shaped responses. Real-world UX
|
|
1846
|
-
// problem 2026-05-06: agent finishes a turn with "
|
|
1849
|
+
// problem 2026-05-06: agent finishes a turn with "Should I look up X?"
|
|
1847
1850
|
// and stops; the user reads the silence as "Franklin died" twice in
|
|
1848
1851
|
// one hour. The Ink input box is already on screen but it's easy to
|
|
1849
1852
|
// miss after a long output scroll. A single trailing italic line
|
|
@@ -64,15 +64,15 @@ Anti-slop rules:
|
|
|
64
64
|
- Wrap literal text that must appear in the image in double quotes. Spell difficult words letter-by-letter.
|
|
65
65
|
- One revision per turn — do not combine conflicting asks.
|
|
66
66
|
- Natural language, not keyword-tag format.
|
|
67
|
-
- refined_prompt stays in the same language as the user input.
|
|
67
|
+
- refined_prompt stays in the same language as the user input.
|
|
68
68
|
|
|
69
69
|
Examples:
|
|
70
70
|
|
|
71
71
|
Input: "a photo of a cat on Mars, photoreal"
|
|
72
72
|
Output: {"style":"photoreal","priority":"balanced","refined_prompt":"Eye-level photograph of a cat standing on the rust-colored Martian surface, late-afternoon low sun casting long shadows, distant canyon rim in the background, 50mm feel, shallow depth of field, editorial photo use, no watermark.","refinement_summary":"Added scene, lighting, lens, use case, constraint.","recommended":{"model":"google/nano-banana-pro","rationale":"Photoreal scenes — Nano Banana Pro has strong realism at moderate cost."},"cheaper":{"model":"google/nano-banana","rationale":"Same family, lower cost, slightly less detail."},"premium":{"model":"openai/gpt-image-2","rationale":"Best photoreal fidelity when budget allows."}}
|
|
73
73
|
|
|
74
|
-
Input: "
|
|
75
|
-
Output: {"style":"anime","priority":"balanced","refined_prompt":"
|
|
74
|
+
Input: "cyberpunk-style anime character"
|
|
75
|
+
Output: {"style":"anime","priority":"balanced","refined_prompt":"Cyberpunk-style anime character standing on a neon-lit rainy street at night, wearing a synthetic-fiber jacket with metallic reflective accents, holographic billboards floating overhead, low-angle view, strong cyan-and-pink contrast, poster use, centered composition.","refinement_summary":"Added scene, lighting, materials, use case, composition.","recommended":{"model":"zai/cogview-4","rationale":"CogView-4 specializes in stylized/anime imagery."},"cheaper":{"model":"google/nano-banana","rationale":"Cheaper but less stylized."},"premium":{"model":"xai/grok-imagine-image-pro","rationale":"Premium detail for complex scenes."}}
|
|
76
76
|
|
|
77
77
|
Input: "a 10-second cinematic drone shot over Tokyo at night"
|
|
78
78
|
Output: {"style":"concept","priority":"quality","refined_prompt":null,"refinement_summary":"Already well-specified.","recommended":{"model":"bytedance/seedance-2.0","rationale":"Seedance 2.0 delivers the best cinematic quality."},"cheaper":{"model":"bytedance/seedance-2.0-fast","rationale":"Faster + cheaper, minor quality trade-off."},"premium":{"model":null,"rationale":"2.0 is already the top tier."}}
|
|
@@ -84,10 +84,10 @@ asksForLiveData: true | false
|
|
|
84
84
|
## Context anchors in input
|
|
85
85
|
|
|
86
86
|
[CURRENT] user's message this turn (primary signal)
|
|
87
|
-
[PREV_REPLY] last assistant reply, first ~300 chars (for follow-up references: "
|
|
87
|
+
[PREV_REPLY] last assistant reply, first ~300 chars (for follow-up references: "and that one?", "the other ticker", "what about AAPL")
|
|
88
88
|
[GOAL] original session prompt, first ~200 chars
|
|
89
89
|
|
|
90
|
-
If [CURRENT] uses a deictic ("it", "that", "
|
|
90
|
+
If [CURRENT] uses a deictic ("it", "that", "the other one", or any equivalent in the user's language), resolve intent/tier from [PREV_REPLY] or [GOAL].
|
|
91
91
|
|
|
92
92
|
## Examples
|
|
93
93
|
|
|
@@ -100,17 +100,17 @@ Input:
|
|
|
100
100
|
Output: {"tier":"COMPLEX","intent":{"kind":"ticker","symbol":"CRCL","assetClass":"stock","market":"us","wantNews":true},"needsPlanning":false,"isPushback":false,"asksForLiveData":true}
|
|
101
101
|
|
|
102
102
|
Input:
|
|
103
|
-
[CURRENT]
|
|
104
|
-
[PREV_REPLY] CRCL
|
|
103
|
+
[CURRENT] what about AAPL
|
|
104
|
+
[PREV_REPLY] CRCL price $96.18, recently down on Drift lawsuit news...
|
|
105
105
|
Output: {"tier":"COMPLEX","intent":{"kind":"ticker","symbol":"AAPL","assetClass":"stock","market":"us","wantNews":false},"needsPlanning":false,"isPushback":false,"asksForLiveData":true}
|
|
106
106
|
|
|
107
107
|
Input:
|
|
108
|
-
[CURRENT] BTC
|
|
108
|
+
[CURRENT] why did BTC drop
|
|
109
109
|
Output: {"tier":"COMPLEX","intent":{"kind":"ticker","symbol":"BTC","assetClass":"crypto","wantNews":true},"needsPlanning":false,"isPushback":false,"asksForLiveData":true}
|
|
110
110
|
|
|
111
111
|
Input:
|
|
112
|
-
[CURRENT]
|
|
113
|
-
[PREV_REPLY] AAPL
|
|
112
|
+
[CURRENT] no, you should be looking at NVDA, not AAPL
|
|
113
|
+
[PREV_REPLY] AAPL price $186.42
|
|
114
114
|
Output: {"tier":"COMPLEX","intent":{"kind":"ticker","symbol":"NVDA","assetClass":"stock","market":"us","wantNews":false},"needsPlanning":false,"isPushback":true,"asksForLiveData":true}
|
|
115
115
|
|
|
116
116
|
Input:
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* Tools (ContentCreate / ContentAddAsset) write the library during agent
|
|
6
6
|
* sessions; before this command, there was no way to see the resulting
|
|
7
7
|
* spend without scripting against the JSON file. Verified 2026-05-04 in
|
|
8
|
-
* a live session: user asked "
|
|
9
|
-
* `franklin content list` and got "no content subcommand", fell
|
|
10
|
-
* estimating from memory.
|
|
8
|
+
* a live session: user asked "how much did I spend making this", agent
|
|
9
|
+
* ran `franklin content list` and got "no content subcommand", fell
|
|
10
|
+
* back to estimating from memory.
|
|
11
11
|
*
|
|
12
12
|
* Subcommands:
|
|
13
13
|
* - list : table of id, type, title, status, spent/budget, assets
|
package/dist/commands/content.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* Tools (ContentCreate / ContentAddAsset) write the library during agent
|
|
6
6
|
* sessions; before this command, there was no way to see the resulting
|
|
7
7
|
* spend without scripting against the JSON file. Verified 2026-05-04 in
|
|
8
|
-
* a live session: user asked "
|
|
9
|
-
* `franklin content list` and got "no content subcommand", fell
|
|
10
|
-
* estimating from memory.
|
|
8
|
+
* a live session: user asked "how much did I spend making this", agent
|
|
9
|
+
* ran `franklin content list` and got "no content subcommand", fell
|
|
10
|
+
* back to estimating from memory.
|
|
11
11
|
*
|
|
12
12
|
* Subcommands:
|
|
13
13
|
* - list : table of id, type, title, status, spent/budget, assets
|
package/dist/commands/start.js
CHANGED
|
@@ -509,25 +509,12 @@ async function runWithInkUI(agentConfig, model, workDir, version, walletInfo, on
|
|
|
509
509
|
recordLatestSessionIfEnabled(process.cwd(), agentConfig.chain);
|
|
510
510
|
}
|
|
511
511
|
catch { /* telemetry is best-effort */ }
|
|
512
|
-
//
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
const { extractBrainEntities } = await import('../brain/extract.js');
|
|
517
|
-
const { ModelClient } = await import('../agent/llm.js');
|
|
518
|
-
const client = new ModelClient({ apiUrl: agentConfig.apiUrl, chain: agentConfig.chain });
|
|
519
|
-
const sid = `session-${new Date().toISOString()}`;
|
|
520
|
-
await Promise.race([
|
|
521
|
-
Promise.all([
|
|
522
|
-
extractLearnings(sessionHistory, sid, client),
|
|
523
|
-
extractBrainEntities(sessionHistory, sid, client),
|
|
524
|
-
]),
|
|
525
|
-
new Promise(resolve => setTimeout(resolve, 15_000)),
|
|
526
|
-
]);
|
|
527
|
-
}
|
|
528
|
-
catch { /* extraction is best-effort */ }
|
|
512
|
+
// Optional post-session learning extraction. Disabled by default because any
|
|
513
|
+
// network-backed background promise can keep Node alive after the UI exits.
|
|
514
|
+
if (process.env.FRANKLIN_EXTRACT_ON_EXIT === '1') {
|
|
515
|
+
runExitBackgroundTasks(sessionHistory, agentConfig).catch(() => { });
|
|
529
516
|
}
|
|
530
|
-
|
|
517
|
+
disconnectMcpServers().catch(() => { });
|
|
531
518
|
// Session summary — delta vs. snapshot at session start
|
|
532
519
|
try {
|
|
533
520
|
const delta = statsDelta(startSnapshot);
|
|
@@ -559,6 +546,19 @@ async function runWithInkUI(agentConfig, model, workDir, version, walletInfo, on
|
|
|
559
546
|
}
|
|
560
547
|
console.log(chalk.dim('\nGoodbye.\n'));
|
|
561
548
|
}
|
|
549
|
+
async function runExitBackgroundTasks(sessionHistory, agentConfig) {
|
|
550
|
+
if (!sessionHistory || sessionHistory.length < 4)
|
|
551
|
+
return;
|
|
552
|
+
const { extractLearnings } = await import('../learnings/extractor.js');
|
|
553
|
+
const { extractBrainEntities } = await import('../brain/extract.js');
|
|
554
|
+
const { ModelClient } = await import('../agent/llm.js');
|
|
555
|
+
const client = new ModelClient({ apiUrl: agentConfig.apiUrl, chain: agentConfig.chain });
|
|
556
|
+
const sid = `session-${new Date().toISOString()}`;
|
|
557
|
+
await Promise.all([
|
|
558
|
+
extractLearnings(sessionHistory, sid, client),
|
|
559
|
+
extractBrainEntities(sessionHistory, sid, client),
|
|
560
|
+
]);
|
|
561
|
+
}
|
|
562
562
|
// ─── Basic readline UI (piped input) ───────────────────────────────────────
|
|
563
563
|
async function runWithBasicUI(agentConfig, model, workDir, initialInput) {
|
|
564
564
|
const { TerminalUI } = await import('../ui/terminal.js');
|
|
@@ -4,44 +4,42 @@
|
|
|
4
4
|
* using keyword matching from router weights or built-in defaults.
|
|
5
5
|
*/
|
|
6
6
|
// Built-in category keywords (used when no learned weights available)
|
|
7
|
+
// Keyword fast-path uses English only by policy (English-only-source rule).
|
|
8
|
+
// Non-English user queries route through the LLM-level classifier above this
|
|
9
|
+
// fast-path, which is multilingual and handles intent correctly without
|
|
10
|
+
// needing a per-language keyword list here.
|
|
7
11
|
const DEFAULT_CATEGORY_KEYWORDS = {
|
|
8
12
|
coding: [
|
|
9
13
|
'function', 'class', 'import', 'def', 'SELECT', 'async', 'await',
|
|
10
14
|
'const', 'let', 'var', 'return', '```', 'bug', 'error', 'fix',
|
|
11
15
|
'refactor', 'implement', 'test', 'npm', 'pip', 'git', 'deploy',
|
|
12
16
|
'API', 'endpoint', 'database', 'query', 'migration', 'lint',
|
|
13
|
-
'函数', '类', '导入', '修复', '调试', '部署',
|
|
14
17
|
],
|
|
15
18
|
trading: [
|
|
16
19
|
'BTC', 'ETH', 'SOL', 'bitcoin', 'ethereum', 'solana', 'crypto',
|
|
17
20
|
'price', 'market', 'signal', 'trade', 'buy', 'sell', 'RSI',
|
|
18
21
|
'MACD', 'volume', 'bullish', 'bearish', 'support', 'resistance',
|
|
19
22
|
'portfolio', 'risk', 'leverage', 'DeFi', 'token', 'swap',
|
|
20
|
-
'比特币', '以太坊', '价格', '市场', '交易', '信号',
|
|
21
23
|
],
|
|
22
24
|
reasoning: [
|
|
23
25
|
'prove', 'theorem', 'derive', 'step by step', 'chain of thought',
|
|
24
26
|
'formally', 'mathematical', 'proof', 'logically', 'analyze',
|
|
25
27
|
'compare', 'evaluate', 'trade-off', 'pros and cons', 'why',
|
|
26
28
|
'explain why', 'reasoning', 'logic', 'deduce', 'infer',
|
|
27
|
-
'证明', '定理', '推导', '分析', '比较',
|
|
28
29
|
],
|
|
29
30
|
creative: [
|
|
30
31
|
'write a story', 'poem', 'creative', 'brainstorm', 'imagine',
|
|
31
32
|
'generate an image', 'design', 'logo', 'illustration', 'art',
|
|
32
33
|
'narrative', 'fiction', 'song', 'lyrics', 'slogan', 'tagline',
|
|
33
|
-
'写一个故事', '诗', '创意', '设计', '头脑风暴',
|
|
34
34
|
],
|
|
35
35
|
research: [
|
|
36
36
|
'search', 'find', 'look up', 'what is', 'who is', 'when was',
|
|
37
37
|
'summarize', 'report', 'overview', 'comparison', 'review',
|
|
38
38
|
'article', 'paper', 'study', 'data', 'statistics', 'trend',
|
|
39
|
-
'搜索', '查找', '什么是', '总结', '报告',
|
|
40
39
|
],
|
|
41
40
|
chat: [
|
|
42
41
|
'hello', 'hi', 'thanks', 'thank you', 'how are you', 'help',
|
|
43
42
|
'translate', 'yes', 'no', 'ok', 'sure', 'good',
|
|
44
|
-
'你好', '谢谢', '帮我', '翻译',
|
|
45
43
|
],
|
|
46
44
|
};
|
|
47
45
|
/**
|
package/dist/router/index.js
CHANGED
|
@@ -70,13 +70,18 @@ const AUTO_TIERS = {
|
|
|
70
70
|
},
|
|
71
71
|
};
|
|
72
72
|
// ─── Keywords for Classification ───
|
|
73
|
+
//
|
|
74
|
+
// Keyword fast-path uses English only by policy (English-only-source rule).
|
|
75
|
+
// Non-English user queries route through the LLM-level classifier above this
|
|
76
|
+
// fast-path, which is multilingual and handles intent correctly without
|
|
77
|
+
// needing per-language keyword lists here.
|
|
73
78
|
const CODE_KEYWORDS = [
|
|
74
79
|
'function', 'class', 'import', 'def', 'SELECT', 'async', 'await',
|
|
75
|
-
'const', 'let', 'var', 'return', '```',
|
|
80
|
+
'const', 'let', 'var', 'return', '```',
|
|
76
81
|
];
|
|
77
82
|
const REASONING_KEYWORDS = [
|
|
78
83
|
'prove', 'theorem', 'derive', 'step by step', 'chain of thought',
|
|
79
|
-
'formally', 'mathematical', 'proof', 'logically',
|
|
84
|
+
'formally', 'mathematical', 'proof', 'logically',
|
|
80
85
|
];
|
|
81
86
|
const SIMPLE_KEYWORDS = [
|
|
82
87
|
// True simple intents: greeting, definition lookup, translation. Factual
|
|
@@ -84,7 +89,7 @@ const SIMPLE_KEYWORDS = [
|
|
|
84
89
|
// because they look easy but require external recall — sending them to
|
|
85
90
|
// SIMPLE-tier models reliably produces hallucinated subscriber counts,
|
|
86
91
|
// birth years, etc. that the post-hoc grounding check then has to flag.
|
|
87
|
-
'define', 'translate', 'hello', 'yes or no',
|
|
92
|
+
'define', 'translate', 'hello', 'yes or no',
|
|
88
93
|
];
|
|
89
94
|
// Research / fact-retrieval intent: questions whose correct answer depends
|
|
90
95
|
// on data the model can't reliably recall from weights — current statistics,
|
|
@@ -98,19 +103,16 @@ const RESEARCH_KEYWORDS = [
|
|
|
98
103
|
'best', 'top ', 'most popular', 'compare', 'vs ', ' vs.',
|
|
99
104
|
'latest', 'current', 'recent', 'today', 'now',
|
|
100
105
|
'subscribers', 'members', 'followers', 'market cap', 'price of',
|
|
101
|
-
'最好的', '最新', '最近', '现在', '当前', '排名', '对比',
|
|
102
106
|
];
|
|
103
107
|
const TECHNICAL_KEYWORDS = [
|
|
104
108
|
'algorithm', 'optimize', 'architecture', 'distributed', 'kubernetes',
|
|
105
|
-
'microservice', 'database', 'infrastructure',
|
|
109
|
+
'microservice', 'database', 'infrastructure',
|
|
106
110
|
];
|
|
107
111
|
const AGENTIC_KEYWORDS = [
|
|
108
112
|
'read file', 'edit', 'modify', 'update', 'create file', 'execute',
|
|
109
113
|
'deploy', 'install', 'npm', 'pip', 'fix', 'debug', 'verify',
|
|
110
114
|
'commit', 'push', 'pull', 'merge', 'rename', 'replace', 'delete',
|
|
111
115
|
'remove', 'add', 'change', 'move', 'refactor', 'migrate',
|
|
112
|
-
'编辑', '修改', '部署', '安装', '修复', '调试',
|
|
113
|
-
'更新', '替换', '删除', '添加', '提交', '改',
|
|
114
116
|
];
|
|
115
117
|
// URL patterns that signal agentic/coding tasks
|
|
116
118
|
const AGENTIC_URL_PATTERNS = [
|
|
@@ -223,7 +225,7 @@ function classifyRequest(prompt, tokenCount) {
|
|
|
223
225
|
// Imperative verbs (build, create, implement, etc.)
|
|
224
226
|
const imperativeMatches = countMatches(prompt, [
|
|
225
227
|
'build', 'create', 'implement', 'design', 'develop', 'write', 'make',
|
|
226
|
-
'generate', 'construct',
|
|
228
|
+
'generate', 'construct',
|
|
227
229
|
]);
|
|
228
230
|
if (imperativeMatches >= 1) {
|
|
229
231
|
score += 0.15;
|
package/dist/social/a11y.d.ts
CHANGED
|
@@ -51,4 +51,4 @@ export declare function extractArticleBlocks(tree: string): Array<{
|
|
|
51
51
|
* This doubles as the "this is a tweet" signal in social-bot — the only link
|
|
52
52
|
* inside an article block with this label shape is the permalink to the tweet.
|
|
53
53
|
*/
|
|
54
|
-
export declare const X_TIME_LINK_PATTERN = "(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s+\\d+(?:,?\\s+\\d{4})?|\\d+[smhd]|\\d+\\s+(?:second|minute|hour|day|week|month|year)s?\\s+ago|just now|now|yesterday|\\d{1,2}:\\d{2}\\s*[AaPp][Mm]|\\d{4}
|
|
54
|
+
export declare const X_TIME_LINK_PATTERN = "(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s+\\d+(?:,?\\s+\\d{4})?|\\d+[smhd]|\\d+\\s+(?:second|minute|hour|day|week|month|year)s?\\s+ago|just now|now|yesterday|\\d{1,2}:\\d{2}\\s*[AaPp][Mm]|\\d{4}\\u5e74\\d{1,2}\\u6708\\d{1,2}\\u65e5";
|
package/dist/social/a11y.js
CHANGED
|
@@ -86,8 +86,12 @@ export function extractArticleBlocks(tree) {
|
|
|
86
86
|
// Matches all known X time-link formats:
|
|
87
87
|
// "Mar 16", "Apr 12, 2026", "5h", "5m", "2d", "30s", "just now", "now"
|
|
88
88
|
// "31 seconds ago", "35 minutes ago", "4 hours ago" (full-word format)
|
|
89
|
-
// "Yesterday", "Apr 12", "12:30 AM"
|
|
90
|
-
|
|
89
|
+
// "Yesterday", "Apr 12", "12:30 AM"
|
|
90
|
+
// CJK-locale date markers (year/month/day in Chinese-locale rendering of
|
|
91
|
+
// tweet timestamps). Encoded via Unicode escapes to keep the source file
|
|
92
|
+
// ASCII-clean per the English-only-source policy:
|
|
93
|
+
// U+5E74 = year marker, U+6708 = month marker, U+65E5 = day marker.
|
|
94
|
+
export const X_TIME_LINK_PATTERN = '(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s+\\d+(?:,?\\s+\\d{4})?|\\d+[smhd]|\\d+\\s+(?:second|minute|hour|day|week|month|year)s?\\s+ago|just now|now|yesterday|\\d{1,2}:\\d{2}\\s*[AaPp][Mm]|\\d{4}\\u5e74\\d{1,2}\\u6708\\d{1,2}\\u65e5';
|
|
91
95
|
function escapeRegex(s) {
|
|
92
96
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
93
97
|
}
|
package/dist/tools/prediction.js
CHANGED
|
@@ -825,7 +825,7 @@ export const predictionMarketCapability = {
|
|
|
825
825
|
'Default routing: ' +
|
|
826
826
|
'"is there a market on X anywhere" → searchAll. ' +
|
|
827
827
|
'"top wallets / who is profitable / who should I follow on Polymarket" → leaderboard. ' +
|
|
828
|
-
'"analyze this wallet / can I copy this trader /
|
|
828
|
+
'"analyze this wallet / can I copy this trader / show me their P&L AND positions" → run walletProfile + walletPnl + walletPositions IN PARALLEL with the same address — three $0.005 calls give the full picture for $0.015. Do not Bash-curl Polymarket directly; the agent has paid tools for this. ' +
|
|
829
829
|
'"what are smart traders betting on right now" → smartActivity. ' +
|
|
830
830
|
'"show smart money on this specific Polymarket market" → smartMoney with conditionId. ' +
|
|
831
831
|
'"should I bet on X" → run searchPolymarket + searchKalshi in parallel and compare implied probabilities — divergence is the signal.',
|
package/dist/tools/searchx.js
CHANGED
|
@@ -13,14 +13,14 @@ import { detectProduct } from '../social/ai.js';
|
|
|
13
13
|
import { loadConfig, isConfigReady } from '../social/config.js';
|
|
14
14
|
import { browserPool } from '../social/browser-pool.js';
|
|
15
15
|
// ─── Intent detection (code-level, not LLM-level) ──────────────────────────
|
|
16
|
-
// When the user asks "check my @handle mentions/notifications
|
|
17
|
-
//
|
|
16
|
+
// When the user asks "check my @handle mentions/notifications", the tool
|
|
17
|
+
// itself routes to x.com/notifications. English-only keyword fast-path;
|
|
18
|
+
// the LLM-level classifier handles non-English queries before this point.
|
|
18
19
|
const NOTIFICATION_KEYWORDS = [
|
|
19
20
|
'notification', 'notifications',
|
|
20
21
|
'mention', 'mentions', 'mentioned',
|
|
21
22
|
'reply', 'replies',
|
|
22
23
|
'interact', 'interaction', 'interactions',
|
|
23
|
-
'互动', '通知', '提及', '回复', '看看',
|
|
24
24
|
'check my', 'my account', 'my x',
|
|
25
25
|
'to:', 'from:', '@',
|
|
26
26
|
];
|
package/dist/tools/wallet.js
CHANGED
|
@@ -50,7 +50,7 @@ export const walletCapability = {
|
|
|
50
50
|
spec: {
|
|
51
51
|
name: 'Wallet',
|
|
52
52
|
description: 'Read Franklin\'s wallet status — chain, address, and USDC balance. ' +
|
|
53
|
-
'Use this for any "what\'s my balance / how much money /
|
|
53
|
+
'Use this for any "what\'s my balance / how much money / wallet status" question. ' +
|
|
54
54
|
'Cheaper and more direct than running `franklin balance` via Bash, and never costs USDC.',
|
|
55
55
|
input_schema: {
|
|
56
56
|
type: 'object',
|
package/package.json
CHANGED