@blockrun/franklin 3.15.41 → 3.15.43
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/loop.js +9 -2
- package/dist/tasks/paths.d.ts +16 -0
- package/dist/tasks/paths.js +35 -2
- package/dist/tasks/store.js +28 -14
- package/package.json +1 -1
package/dist/agent/loop.js
CHANGED
|
@@ -1236,9 +1236,16 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
|
|
|
1236
1236
|
savings: routingSavings,
|
|
1237
1237
|
contextPct: Math.round(contextUsagePct),
|
|
1238
1238
|
});
|
|
1239
|
-
// Record usage for stats tracking (franklin stats command)
|
|
1239
|
+
// Record usage for stats tracking (franklin stats command).
|
|
1240
|
+
// Pass the fallback flag so franklin-stats.json's totalFallbacks +
|
|
1241
|
+
// per-model fallbackCount stay in sync with the audit log a few
|
|
1242
|
+
// lines below — same `turnFailedModels.size > 0` predicate, same
|
|
1243
|
+
// turn. Without this, stats showed 0 fallbacks across 5150 real
|
|
1244
|
+
// requests on a machine that visibly hit fallback paths in
|
|
1245
|
+
// franklin-debug.log; `franklin insights` was therefore useless
|
|
1246
|
+
// for spotting a hot routing chain.
|
|
1240
1247
|
const costEstimate = estimateCost(resolvedModel, inputTokens, usage.outputTokens, 1);
|
|
1241
|
-
recordUsage(resolvedModel, inputTokens, usage.outputTokens, costEstimate, 0);
|
|
1248
|
+
recordUsage(resolvedModel, inputTokens, usage.outputTokens, costEstimate, 0, turnFailedModels.size > 0);
|
|
1242
1249
|
// ── Circuit breakers: prevent infinite-loop wallet drain ──
|
|
1243
1250
|
// Per-turn $-cap was removed in v3.11.0 — runaway loops are caught by
|
|
1244
1251
|
// MAX_TOOL_CALLS_PER_TURN (25) and MAX_TINY_RESPONSES (2) above; the
|
package/dist/tasks/paths.d.ts
CHANGED
|
@@ -3,8 +3,24 @@
|
|
|
3
3
|
* meta.json — single TaskRecord, atomically rewritten
|
|
4
4
|
* events.jsonl — append-only event log
|
|
5
5
|
* log.txt — child process stdout/stderr
|
|
6
|
+
*
|
|
7
|
+
* Storage location: defaults to BLOCKRUN_DIR (~/.blockrun), matching
|
|
8
|
+
* every other persistent state in the codebase (sessions, audit, stats,
|
|
9
|
+
* brain, etc.). Earlier releases used ~/.franklin instead, so we
|
|
10
|
+
* lazily fall back to that legacy directory on reads when a task isn't
|
|
11
|
+
* found in the primary location. New tasks always write to the primary.
|
|
12
|
+
*
|
|
13
|
+
* Why a lazy fallback instead of a startup migration: a long-running
|
|
14
|
+
* task runner (`franklin _task-runner <runId>`) captures its task dir
|
|
15
|
+
* path in memory at spawn and continues writing there for the duration
|
|
16
|
+
* of the run. Verified 2026-05-04: an in-flight ETL task at PID 59095
|
|
17
|
+
* had been writing to ~/.franklin/tasks/ for 4 minutes, with ~10 hours
|
|
18
|
+
* of progress still ahead. Moving the directory mid-flight would
|
|
19
|
+
* orphan its writes; the fallback path lets new CLI commands keep
|
|
20
|
+
* reading legacy task state without disturbing an active runner.
|
|
6
21
|
*/
|
|
7
22
|
export declare function getTasksDir(): string;
|
|
23
|
+
export declare function getLegacyTasksDir(): string;
|
|
8
24
|
export declare function getTaskDir(runId: string): string;
|
|
9
25
|
export declare function ensureTaskDir(runId: string): string;
|
|
10
26
|
export declare function taskMetaPath(runId: string): string;
|
package/dist/tasks/paths.js
CHANGED
|
@@ -3,18 +3,51 @@
|
|
|
3
3
|
* meta.json — single TaskRecord, atomically rewritten
|
|
4
4
|
* events.jsonl — append-only event log
|
|
5
5
|
* log.txt — child process stdout/stderr
|
|
6
|
+
*
|
|
7
|
+
* Storage location: defaults to BLOCKRUN_DIR (~/.blockrun), matching
|
|
8
|
+
* every other persistent state in the codebase (sessions, audit, stats,
|
|
9
|
+
* brain, etc.). Earlier releases used ~/.franklin instead, so we
|
|
10
|
+
* lazily fall back to that legacy directory on reads when a task isn't
|
|
11
|
+
* found in the primary location. New tasks always write to the primary.
|
|
12
|
+
*
|
|
13
|
+
* Why a lazy fallback instead of a startup migration: a long-running
|
|
14
|
+
* task runner (`franklin _task-runner <runId>`) captures its task dir
|
|
15
|
+
* path in memory at spawn and continues writing there for the duration
|
|
16
|
+
* of the run. Verified 2026-05-04: an in-flight ETL task at PID 59095
|
|
17
|
+
* had been writing to ~/.franklin/tasks/ for 4 minutes, with ~10 hours
|
|
18
|
+
* of progress still ahead. Moving the directory mid-flight would
|
|
19
|
+
* orphan its writes; the fallback path lets new CLI commands keep
|
|
20
|
+
* reading legacy task state without disturbing an active runner.
|
|
6
21
|
*/
|
|
7
22
|
import fs from 'node:fs';
|
|
8
23
|
import os from 'node:os';
|
|
9
24
|
import path from 'node:path';
|
|
25
|
+
import { BLOCKRUN_DIR } from '../config.js';
|
|
26
|
+
const LEGACY_FRANKLIN_HOME = path.join(os.homedir(), '.franklin');
|
|
10
27
|
function franklinHome() {
|
|
11
|
-
return process.env.FRANKLIN_HOME ||
|
|
28
|
+
return process.env.FRANKLIN_HOME || BLOCKRUN_DIR;
|
|
12
29
|
}
|
|
13
30
|
export function getTasksDir() {
|
|
14
31
|
return path.join(franklinHome(), 'tasks');
|
|
15
32
|
}
|
|
33
|
+
export function getLegacyTasksDir() {
|
|
34
|
+
return path.join(LEGACY_FRANKLIN_HOME, 'tasks');
|
|
35
|
+
}
|
|
16
36
|
export function getTaskDir(runId) {
|
|
17
|
-
|
|
37
|
+
// Prefer the primary location. If a task already exists in the
|
|
38
|
+
// legacy ~/.franklin/tasks/ — either created by an older release or
|
|
39
|
+
// by a runner subprocess started before this version was installed —
|
|
40
|
+
// continue to read/write there until it completes, so we don't strand
|
|
41
|
+
// its in-flight events.jsonl + meta.json writes.
|
|
42
|
+
const primary = path.join(getTasksDir(), runId);
|
|
43
|
+
if (fs.existsSync(primary))
|
|
44
|
+
return primary;
|
|
45
|
+
if (process.env.FRANKLIN_HOME === undefined) {
|
|
46
|
+
const legacy = path.join(getLegacyTasksDir(), runId);
|
|
47
|
+
if (fs.existsSync(legacy))
|
|
48
|
+
return legacy;
|
|
49
|
+
}
|
|
50
|
+
return primary;
|
|
18
51
|
}
|
|
19
52
|
export function ensureTaskDir(runId) {
|
|
20
53
|
const dir = getTaskDir(runId);
|
package/dist/tasks/store.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* tolerant of a torn last line.
|
|
17
17
|
*/
|
|
18
18
|
import fs from 'node:fs';
|
|
19
|
-
import { ensureTaskDir, taskMetaPath, taskEventsPath, getTasksDir, } from './paths.js';
|
|
19
|
+
import { ensureTaskDir, taskMetaPath, taskEventsPath, getTasksDir, getLegacyTasksDir, } from './paths.js';
|
|
20
20
|
export function writeTaskMeta(record) {
|
|
21
21
|
ensureTaskDir(record.runId);
|
|
22
22
|
const target = taskMetaPath(record.runId);
|
|
@@ -103,21 +103,35 @@ export function applyEvent(runId, event) {
|
|
|
103
103
|
return next;
|
|
104
104
|
}
|
|
105
105
|
export function listTasks() {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
// Walk both the primary tasks dir and the legacy ~/.franklin/tasks/
|
|
107
|
+
// location so `franklin task list` keeps showing legacy tasks until
|
|
108
|
+
// their dirs are cleaned up. Dedupe by runId (first-wins, primary
|
|
109
|
+
// ordered first) — protects against the unlikely case of the same
|
|
110
|
+
// runId existing in both locations.
|
|
111
|
+
const dirs = [getTasksDir()];
|
|
112
|
+
if (process.env.FRANKLIN_HOME === undefined)
|
|
113
|
+
dirs.push(getLegacyTasksDir());
|
|
114
|
+
const seen = new Set();
|
|
113
115
|
const out = [];
|
|
114
|
-
for (const
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
for (const dir of dirs) {
|
|
117
|
+
let entries;
|
|
118
|
+
try {
|
|
119
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
117
122
|
continue;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
123
|
+
}
|
|
124
|
+
for (const ent of entries) {
|
|
125
|
+
// Skip junk like .DS_Store — only real per-task subdirectories are valid.
|
|
126
|
+
if (!ent.isDirectory())
|
|
127
|
+
continue;
|
|
128
|
+
if (seen.has(ent.name))
|
|
129
|
+
continue;
|
|
130
|
+
seen.add(ent.name);
|
|
131
|
+
const meta = readTaskMeta(ent.name);
|
|
132
|
+
if (meta)
|
|
133
|
+
out.push(meta);
|
|
134
|
+
}
|
|
121
135
|
}
|
|
122
136
|
out.sort((a, b) => b.createdAt - a.createdAt);
|
|
123
137
|
return out;
|
package/package.json
CHANGED