@blockrun/franklin 3.15.15 → 3.15.16

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.
@@ -1201,6 +1201,11 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
1201
1201
  inputTokens,
1202
1202
  outputTokens: usage.outputTokens,
1203
1203
  costUsd: costEstimate,
1204
+ // Any failed model this turn means the model that finally
1205
+ // succeeded was a fallback. Without this, audit log read 0%
1206
+ // fallbacks across 4k entries — useless for diagnosing whether
1207
+ // the routing chain is healthy or hot.
1208
+ fallback: turnFailedModels.size > 0,
1204
1209
  source: 'agent',
1205
1210
  workDir,
1206
1211
  prompt: extractLastUserPrompt(history),
@@ -11,6 +11,7 @@
11
11
  import fs from 'node:fs';
12
12
  import path from 'node:path';
13
13
  import { BLOCKRUN_DIR } from '../config.js';
14
+ import { isTestFixtureModel } from './test-fixture.js';
14
15
  const AUDIT_FILE = path.join(BLOCKRUN_DIR, 'franklin-audit.jsonl');
15
16
  const PROMPT_PREVIEW_CHARS = 240;
16
17
  // Cap the audit log at the most recent N entries. Without this the file
@@ -26,6 +27,12 @@ const TRIM_PROBE_BYTES = MAX_AUDIT_ENTRIES * 200;
26
27
  const TRIM_CHECK_INTERVAL = 200;
27
28
  let appendsSinceCheck = 0;
28
29
  export function appendAudit(entry) {
30
+ // Tests run interactiveSession() in-process with model="local/test*"
31
+ // and would otherwise pollute the user's real audit log. Drop the
32
+ // entry before any disk write rather than relying on every test to
33
+ // remember to redirect HOME.
34
+ if (isTestFixtureModel(entry.model))
35
+ return;
29
36
  try {
30
37
  fs.mkdirSync(BLOCKRUN_DIR, { recursive: true });
31
38
  const safe = {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Test-fixture model detection.
3
+ *
4
+ * Tests in `test/local.mjs` run `interactiveSession()` in-process with
5
+ * model names like `local/test-model` and `local/test`. The agent loop
6
+ * persists every successful turn to `~/.blockrun/franklin-audit.jsonl`,
7
+ * `franklin-stats.json`, and the session store — which means tests
8
+ * pollute the user's real telemetry. Verified on a real machine:
9
+ * 2326 of 3969 audit entries (58.6%) and 84 of 1000 stats entries
10
+ * (8.4%) were `local/test*` test fixtures.
11
+ *
12
+ * The fix is to skip persistence when the model name follows the
13
+ * convention. Test prefixes are reserved (`local/test*` won't ever ship
14
+ * as a real model on the BlockRun gateway), so this is safe.
15
+ *
16
+ * Local LLMs that real users run (`local/llamafile`, `local/ollama`,
17
+ * `local/lmstudio`, etc.) are intentionally NOT filtered — only the
18
+ * `local/test` prefix.
19
+ */
20
+ export declare function isTestFixtureModel(model: string | undefined | null): boolean;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Test-fixture model detection.
3
+ *
4
+ * Tests in `test/local.mjs` run `interactiveSession()` in-process with
5
+ * model names like `local/test-model` and `local/test`. The agent loop
6
+ * persists every successful turn to `~/.blockrun/franklin-audit.jsonl`,
7
+ * `franklin-stats.json`, and the session store — which means tests
8
+ * pollute the user's real telemetry. Verified on a real machine:
9
+ * 2326 of 3969 audit entries (58.6%) and 84 of 1000 stats entries
10
+ * (8.4%) were `local/test*` test fixtures.
11
+ *
12
+ * The fix is to skip persistence when the model name follows the
13
+ * convention. Test prefixes are reserved (`local/test*` won't ever ship
14
+ * as a real model on the BlockRun gateway), so this is safe.
15
+ *
16
+ * Local LLMs that real users run (`local/llamafile`, `local/ollama`,
17
+ * `local/lmstudio`, etc.) are intentionally NOT filtered — only the
18
+ * `local/test` prefix.
19
+ */
20
+ const TEST_FIXTURE_PREFIXES = [
21
+ 'local/test',
22
+ ];
23
+ export function isTestFixtureModel(model) {
24
+ if (!model)
25
+ return false;
26
+ for (const prefix of TEST_FIXTURE_PREFIXES) {
27
+ if (model.startsWith(prefix))
28
+ return true;
29
+ }
30
+ return false;
31
+ }
@@ -7,6 +7,7 @@ import path from 'node:path';
7
7
  import os from 'node:os';
8
8
  import { OPUS_PRICING } from '../pricing.js';
9
9
  import { BLOCKRUN_DIR } from '../config.js';
10
+ import { isTestFixtureModel } from './test-fixture.js';
10
11
  let resolvedStatsFile = null;
11
12
  function preferredStatsFile() {
12
13
  return path.join(BLOCKRUN_DIR, 'franklin-stats.json');
@@ -156,6 +157,12 @@ export function flushStats() {
156
157
  * Record a completed request for stats tracking
157
158
  */
158
159
  export function recordUsage(model, inputTokens, outputTokens, costUsd, latencyMs, fallback = false) {
160
+ // Same rationale as appendAudit — tests run in-process with
161
+ // local/test* models and would otherwise mix into franklin-stats.json
162
+ // history (verified: 8.4% of a real user's 1000-entry history was
163
+ // test fixtures before this gate).
164
+ if (isTestFixtureModel(model))
165
+ return;
159
166
  const stats = getCachedStats();
160
167
  const now = Date.now();
161
168
  // Update totals
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.15",
3
+ "version": "3.15.16",
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": {